webpack折腾记(一)
gulp用了很长一段时间了,也挺顺手的,只是最近一直在用vue-cli
进行开发,被其各种方便的特性给惊呆了。然而归根结底就是gulp和webpack的比较,作为新一代的前端流程开发工具(现在也不新了),还是大概了解一下吧。
参考:
概述
webpack与gulp
起初断断续续学了一段时间的webpack,后来终究还是给放弃了,因为之前的项目比较小,用gulp写两个任务搭好环境就可以了(这大概就是温水煮青蛙,不愿离开稳定的环境罢!)。
现在来看看webpack和gulp之间的区别:
gulp
是一个自动化工具,其工作流程是基于文件流的,代替人工手动操作,实现自动化开发webpack
是一个打包工具,将项目中的各种文件合并打包,实现模块化开发
我认为在概念上,gulp和webpack并不冲突,尽管他们可以借助自身的插件或loader实现许多类似的功能,比如:
- 文件压缩打包
- css预编译器
- 热更新环境
那么,为什么我现在要重新折腾webpack呢?因为之前一直在纠结开发过程中的样式表管理,脚本管理等问题,也写过“BEM命名”,requireJS使用方法等文章,说到底就是模块化开发的问题。webpack既能完成大部分自动化功能,更重要的是他解决了模块化开发的问题!但是口说无凭,webpack到底是如何解决模块化的问题的呢?
模块化开发
为了实现模块化开发:
- 在RequireJS中,我们使用
define
定义模块,使用require
引入模块; - 在scss中,我们将样式组件拆分成数个独立的
_*.scss
,使用@import
按需引入相应组件。
webpack是基于nodejs的,也就是说,我们可以使用CommonJS规范来管理JS模块。但是,样式表、图片和其他文件是怎么实现模块化管理的呢?注意,重点来了:webpack的核心思想就是将所有资源都视为JS模块,并允许我们通过相同的方式调用这些模块。
更明白一点讲,webpack把我们的项目当作一个主体,通过一个给定的主文件找到整个项目所有的依赖文件,将这些依赖文件进行处理之后统一打包成一个浏览器可识别的JS文件。是不是很神奇?下面让我们慢慢揭开它的神秘面纱。
使用方法
关于webpack的使用防范,网上也有大量的教程了,这里简单整理一下使用方法(PS:使用的是版本v2.2.1
)。
入口文件
由于是将多个资源模块打包成一个文件(打包成多个文件的做法后面会提到),这意味着我们的页面只需要调用这一个文件就够了。因此我们需要列出项目的依赖模块,这个列出资源列表的文件通常称作入口文件,我一般命名为entry.js
(实际上入口文件可以不只一个哦);
//demo1.js
module.exports = {
writeHello(){
var oDiv = document.createElement("div");
oDiv.innerHTML = "<h1>This div came from demo1.js</h1>";
document.body.appendChild(oDiv);
}
}
// entry.js
var demo1 = require("./lib/demo1.js");
demo1.writeHello();
最后调用webpack entry.js dist/bundle.js
进行打包,输出文件为dist/bundle.js
。可以看见,采用的是跟NodeJS完全相同的模块语法,用文档的话来讲:
Webpack 会分析入口文件,解析包含依赖关系的各个文件。这些文件(模块)都打包到 bundle.js 。Webpack 会给每个模块分配一个唯一的 id 并通过这个 id 索引和访问模块。在页面启动时,会先执行 entry.js 中的代码,其它模块会在运行 require 的时候再执行。
在我们的页面上只需要引入对应的bundle.js
文件就可以了,如果使用equireJS,还必须得配置相关路径,最后使用r.js
进行打包,所以这个确实方便得多。
<script src="dist/bundle.js"></script>
loader
使用CommonJS风格调用js文件是无可厚非的,但是对于其他非js的文件,比如图片样式表等,webpack使用的方案是loader
加载器,使用loader,就可以像调用JS模块一样使用其他类型的资源文件(官方的叫法是any static resource
)。举个打包样式表的例子,准备一个css文件,
// css/demo1.css
body {
background-color: red;
}
然后安装对应loader:
cnpm i style-loader css-loader -D
接着在我们的入口文件引入对应的css资源,并配置相关loader,关于每个loader的作用下面马上讲解(顺道吐槽一下为啥是用!
作为分割符呢?)
// entry.js
require("!style-loader!css-loader!./css/demo1.css");
最后进行打包就可以了。此时打开页面index.html
可以发现,样式表原来是作为style
节点插入页面头部的,这是为什么呢?没错,就是上面的loader的作用。
style-loader
:将css插入到页面的style标签css-loader
:将 css 装载到 javascript
加载顺序 细心的你应该会发现一个问题:为什么先写style-loader
,然后写css-loader
,最后才是*.css
这个资源文件呢?正常情况下不是先加载css资源,然后将css资源装载到js,最后通过js将样式表节点插入页面上吗?
恭喜你!你发现了一个天大的秘密:**loader的加载顺序实际上是从右向左的!**这里跟gulp中的文件流的概念相似,多个loader之间的工作是基于文件流进行的,因此,**必须确保loader的从右向左的加载顺序!**不信你把上面两个loader顺序调换试试。
相同文件配置 有代码洁癖的你应该会发现第二个问题:如果需要打包多个样式表,那不是每次引入都必须为这些css文件配置对应的loader?没错,的确如此,webpack提供了按照文件格式批量配置loader的方案:
webpack entry.js dist/bundle.js --module-bind "css=style-loader!css-loader"
看起来已经解决问题了。但是,每次都打包都配置这个长的参数真的好吗?webpack提供了一个更简单粗暴的方案:配置文件!
配置文件
在gulp中我们使用gulpfile.js
加载模块,配置环境和定义任务,在webpack中我们在webpack.config.js
中定义我们的配置,而最终需要执行的只是一个webpack
打包指令就可以了。尽管这个配置文件仅仅只是一个简单的JS模块,但是相关的配置参数还是比较繁复的,这也正是学习webpack的一个难点。
实际上配置参数也是根据webpack本身的功能来进行的(这不废话吗?)只要了解了webpack几个主要特性,相关的配置就迎刃而解了。我这里先简单整理了初学者需要了解的配置参数,强烈推荐官方文档。
路径
路径配置包括
// 入口文件路径
entry: __dirname + "./entry.js",
// 输入文件
output: {
path: __dirname + "/dist",
filename: "bundle.js"
},
模块
网上大量的教程都是关于module.loaders
的配置,后来才发现那是webpack1的使用方法。在webpack2中,使用module.rules
,个人认为这种配置方式更加直观。
module: {
rules: [
{
// 匹配规则
test: /\.css$/,
// 对应loader
loader: ["style-loader", "css-loader"],
// 其他参数...
include: path.resolve(__dirname, "style"),
exclude: path.resolve(__dirname, "node_modules"),
},
]
},
大部分情况下,我们只需要配置正确的loader就可以 了。
其他
此外还有一些不是特别常见的配置属性
resolve
,配置模块的引入等,比如为模块取个别名externals
,扩展模块库,比如直接引入CDN文件
正确配置了相关的参数之后,打包的命令就只需要一个不带任何参数的webpack
指令就可以了,或者使用npm
指定一个打包命令npm run build
之类的。
- 大部分配置参数都可以是字符串或数组形式:为单参数时用字符串,为多参数时用数组
- 建立使用绝对路径配置文件相关路径,
path.resolve(__dirname, target)
,或者预先定义常量
插件
loader只能让我们像加载js一样加载其他资源,但是还有某些特殊的需求,比如增加注释,打包多文件等,我们需要使用插件来完成。
来看一个简单的例子,在输出文件添加文档注释,这里使用webpack内置的插件BannerPlugin
,通过配置文件的plugins
参数进行配置:
plugins: [
new webpack.BannerPlugin("Author: txm, 2017-08-28")
]
在gulp中并没有loader的概念,因此整个工作是通过文档流合并在一起的。在webpack中,loader可以让我们处理各种格式的文件,而插件可以让我们完成自动化的工作。换句话说,一般情况下常见的文件格式都有其对应的loader进行处理,而具体的任务需求所需要的插件,只能靠自己平常去收集了(汗)...
搭建开发环境
使用webpack可以显著提高开发效率,且只需要进行简单的配置即可,不需要像gulp一样去编写对应的任务。下面从样式表、脚本、热更新、文件处理等方面对相关的配置进行总结
css
样式表的开发流程一般是
- 使用scss编写源码,
- 然后对编译后的css文件进行处理,比如添加浏览器前缀
- 样式表中引用的外部文件,比如图片字体等,webpack也将他们识别为相应的模块,因此也必须配置对应的loader
- 如果需要将css打包进js文件,还必须使用前面提到的一些相关loader
常用loader
scss-loader
,编译scss文件auto-prefixer
,自动处理浏览器前缀px2rem
,将像素单位转换为rem单位url-loader
,用来处理background-image
的url问题,还可以将图片进行base64转码css-loader
,让webpack加载css文件style-loader
,将样式表输出到页面上
常用插件 尽管使用style-loader
将css一起打包到一个js文件看起来很酷,但是考虑到浏览器渲染流程,更通常是将样式表单独提出来,此时可以使用extract-text-webpack-plugin
插件完成。
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: "css-loader"
})
}
]
,
plugins: [
new ExtractTextPlugin("main.css"),
],
javascript
使用webpack编写脚本主要侧重两个方面:
- 模块化
- Babel语法转化
模块化 将js代码拆分成多个模块(包括第三方库文件),然后按需引入,实现模块化的开发,并不需要我们像使用RequireJS一样还得配置路径,声明依赖等,webpack会自动处理这一切。如果想要引入CDN上面的脚本资源,必须现在页面上使用script
标签引入,然后在配置文件的externals
参数中进行配置。
// index.html
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script>
// webpack.config.js
externals: {
jquery: "jQuery"
}
这里的"jQuery"
就是全局变量jQuery
,查看编译后的文件,可以看见内部实现是module.exports = jQuery
,也就是说引入jquery的这个文件是放在引入bundle.js
的前面进行加载的,因为必须保证bundle.js
能够访问到jQuery对象。在接下来的代码中就可以使用var $ = require("jquery")
了。 个人觉得这里有点不合理,RequireJS可以在CDN文档加载失败之后调用备份的本地文件,此外这样的依赖顺序会导致浏览器的阻塞,在webpack这里我还没找到具体的处理措施,先挖个坑吧。
babel 实现babel转义需要安装
babel-core
,babel功能文件babel-preset-env
,根据配置环境智能转换JS代码的版本,而不是一股脑都转换为旧代码
此外还需要对应的loader
babel-loader
然后添加对应的规则就可以了,甚至连.babellrc
都不需要。
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['env'] // 配置需要编译的版本
}
}
}
热更新
在webpack中实现热更新也简单很多,首先全局安装webpack-dev-server
cnpm i webpack-dev-server -g
然后再运行就可以了,不用向gulp那样去配置livereload
这些了,如果闲命令太长也可以在package.json
中封装成npm命令。
webpack-dev-server --progress --colors
遇见热更新不生效的问题,需要将index.html
中的dist/bundle.js
转换为localhost:8080/dist/bundle.js
这样,相关参考文档。
代码压缩
代码压缩这个问题就简单得多了,webpack内置了一个UglifyJsPlugin
插件
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
],
相关的配置参数也请移步文档
小结
至此,简单理清了webpack的使用方法,并简单搭建了一套开发环境。想到之前学习webpack的时候,满世界找教程,都发现教程基本上都是webpack1的攻略,很多配置都“过时”了。这让我明白了一个道理:**官方的文档才是首先应该去翻阅的教程!**在今后的学习中,更应该注意这个问题,至于英语文档啥的这个总是得克服的不是嘛...
你要请我喝一杯奶茶?
版权声明:自由转载-非商用-保持署名和原文链接。
本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。