为什么80%的码农都做不了架构师?>>>
webpack是一款打包工具,它是模块化开发的模式、优化了加载速度;
webpack有什么特色?
这些已有的模块化工具并不能很好的完成如下的目标:
- 将依赖树拆分成按需加载的块
- 初始化加载的耗时尽量少
- 各种静态资源都可以视作模块
- 将第三方库整合成模块的能力
- 可以自定义打包逻辑的能力
- 适合大项目,无论是单页还是多页的 Web 应用
一、初始化配置
npm init //npm 初始化 生成 package.json 配置文件
首先是package.json文件,它不是webpack的组成部分,但是常和webpack项目出双入对,先看一下它的大概模样:
1. scripts 命令行脚本通过key:value的方式描述。key是脚本名,value是脚本执行的内容,通过在命令行中输入npm run 脚本名 就可以执行。这一块的内容是实际开发中很实用的,这里不详情展开,参考地址关于这个文件的更多介绍,请移步官方内容 这里我只重点介绍一下以下内个内容:
常见的脚本名有:npm run start , npm run test 。 内置的脚本名(比如start),可以省略run。
2. devDependencies 开发依赖,相应的还有一个dependencies(可以理解为生产环境依赖)
通过npm install 包名 --save-dev (保存到devDependencies),或 --save 保存到(dependencies)
package.json是用来配合包的管理和发布用的,如果你不想发布这个项目,似乎以上内容对项目开发并没有什么好处,但是作为团队协作,它可以方便自己和同事快速搭建项目,管理项目中用到的第三方包。
二、创建webpack配置文件 webpack.config.js
npm install webpack --save-dev //给项目安装webpack 同时创建webpack.config.js文件
module.exports={
- 单 入口 与 输出 实例
entry:'./js/index.js',
output:{
path:__dirname,(如果指定路径必须是绝对路径)
//__dirname 代表所有打包的bundle.js的文件位置和入口文件index.js 同一个目录下
path:path.join(_dirname,'dist') (把打包文件打入dist目录下边)
filename:'bundle.js'
}
}
2、多页面配置入口实例 与 输出 实例
var path=require('path');
module.exports={
entry: {
bundle1: './main1.js', //入口1
bundle2: './main2.js'//入口2
},
output: {
path:__dirname, (如果指定路径必须是绝对路径)
path:path.join(_dirname,'dist') ,
publicPath:'/dist/' //在对应的html中引入 dist 目录下对应的打包文件
filename: '[name].bundle.js' // [name]是一个变量,会自动替换成entry的key
}
}
这种用法,对于入口文件需要指定多种类型的文件时比较有用。比如['./main1.js','./main1.css'],
后面用到再细讲。
小结一下:对于entry一共展示了三种形式:
1. entry:'app.js' 直接写入口文件
2. entry:{bundle:'./main1.js'} 对象形式
3. entry:{bundle:['./main1.js']} 对象中的值用数组表示
3、多文件合并一个文件
有两个文件,app.js,cats.js,需要把它们合并成一个bundle.js文件
cats.js:
1 2 |
var cats = ['dave', 'henry', 'martha']; module.exports = cats; |
app.js:
1 2 |
cats = require('./cats.js'); console.log(cats); |
webpack.config.js :
3 4 5 6 7 8 9 |
//最简单的webpack配置 module.exports = { entry: './app.js', //入口文件地址 output: { filename: 'bundle.js', //打包后的文件名 } }; |
三、配置常用的loader(加载器)
1)、css loader
npm install css-loader style-loader --save-dev (安装当前项目依赖下)
var path=require('path');
var webpack=require('webpack');
module.exports={
entry: {
bundle1: './main1.js', //入口1
bundle2: './main2.js'//入口2
},
output: {
path:__dirname, //(如果指定路径必须是绝对路径)
path:path.join(_dirname,'dist') , 二个path选一个
publicPath:'/dist/' //在对应的html中引入 dist 目录下对应的打包文件
注意:这个publicPath原本是用来配置虚拟目录用的,也就是通过http方式访问时的路径,或者通过webpack HMR方式加载时的输出目录。在这里只能算是一种hack用法。说到output,就要提一下文件缓存[hash]的用法:
1 2 3 4 5 |
output: { path:'./dist', publicPath:'./dist/', filename: 'bundle_[hash:8].js' //通过:8截取has值的前8位 }, |
这个[hash]作用很少被提到,在实际开发中,是很常见的功能,原样输出的hash我觉得太长,可以通过[hash:加数字]的方式进行截取,很方便。
filename: '[name].bundle.js' // [name]是一个变量,会自动替换成entry的key
},
plugins:[
new webpack.optimize.uglifyJsPlugin() //对所有打包的文件进行压缩
],
module:{
loaders:[
{test:/\.js$/,loader:'babel'},
{test:/\.css$/,loader:'style!css'} //匹配已 .css 为扩展名的文件
//test是对该类文件的正则表达式,用来判断采用这个loader的条件,例如 .css 为扩展名的文件
]
}
3.10.0版本
module:{
rules:[{
{test:/\.css$/} //匹配已 .css 为扩展名的文件
use:[
"style-loader",
"css-loader"
]
}]
}
}
这里有两个要注意的地方:
1。 对于有多个加载器串联的情况,webpack,它是从右向左依赖加载的,也就是说先用css-loader,再用style-loader.
2. 为什么会有一个style-loader, 因为webpack默认是把css文件插在html文件内,通过style标签加载样式的。所以需要用style-loader这个加载器。
如果想要把css用文件的形式link到html中,也是可以的,后面会讲到。
由于我们用了css加载器,所以入口文件其实也可以直接写成:entry:'./app.css'; 效果是一样的。这就体现了,入口文件,不一定要是js格式,只要有对应的加载器,就可以直接在入口中使用,甚至多种类型混合使用,比如['./app.js','app.css'],都是可以的。
webpack 中 css-loader 高阶应用 css作用域 与 css Modules
目的:解决团队开发时类名定义冲突问题
在webpack.config.js 下 css-loader里边配置 modules:true
它的原理是将同样的css 类名 webpack 里边的 hash 把类名替换成唯一的类名 从而避免冲突;
css-loader 高阶应用 自动分离CSS到独立文件
- 首先安装
npm installl extract-text-webpack-plugin --save-dev
2.然后在webpack.config.js 引入extract-text-webpack-plugin 并配置
其中filename 的值 name 是参数 css文件定义的 app.js 那么name就是appl
ignoreOrder 参数 代表 是否跳过排序 默认设置为 true
2)、img loader
module:{
loaders:[
{test:/\.js$/,loader:'babel'},
{test:/\.css$/,loader:'style!css'}, //匹配已 .css 为扩展名的文件
{test:/\.png$/,loader:'file'} , //匹配已 .png为扩展名的文件
{test:/换成文件夹路径images/,loader:'url'}
/*项目中icon比较多的时候用webpack的‘’url‘’ loader 会把图片路径转化base64 从而减少http请求*/
]
}
3)、Sass loader
当我们用sass编写css样式的时候就可以用到
{test:/\.scss$/,loader:'sass'} ,
四、安装sourcemap调试工具
webpack --devtool source-map 为打包的文件生成 .map 文件
在js文件中写入debugger断点
运行webpack 刷新浏览器 出现debugger断点
在webpack配置文件中安装调试工具
例如如下代码:
module.exports={
entry:'./js/index.js',
output:{
path:__dirname,
filename:'bundle.js'
},
devtool:'source-map', //便于debugger调试,否则在浏览器的source中看不到自己的代码都是webpack的代码不利于调试
module:{
loaders:[
{test:/\.css$/,loader:'style!css'} //匹配已 .css 为扩展名的文件
]
}
}
五、安装babel转换器 (在用JSX、ES6等使用)
npm install babel-loader babel-core babel-preset-es2015 --save-dev
然后创建 .babelrc文件 并写入es-2015的预设
例如如下代码:
{
"presets":["es-2015"]
}
然后再webpack.config.js中配置一下什么样的文件需要使用babel
module.exports={
entry:'./js/index.js',
output:{
path:__dirname,
filename:'bundle.js'
},
devtool:'source-map',
module:{
loaders:[
{test:/\.js$/,loader:'babel'},
{test:/\.css$/,loader:'style!css'} //匹配已 .css 为扩展名的文件
]
}
}
六、安装webpack-dev-server 模块热替换功能
- npm install webpack-dev-server - -global 先全局安装
- npm install webpack-dev-server - -save-dev 在安装到项目依赖中
- webpack-dev-server --inline --hot
注意:默认生成服务器:localhost:8080,可以在devServer中更改。每次改代码保存后,会自动编译并且刷新页面。还可以做到热替换即不刷新页面可以更新。除此之外还有一个proxy功能可以用来配置代理。
4.WDS 端口号等配置相关(主要还是用在开发环境,生产环境还是用其他的server的软件即可)
在 module.exports 里添加
devServer:{
host:默认是localhost 也可以写自己的域名
port:nodejs默认是8080 也可以写80或其他 在window系统可能在命令行前添加 sodu
}
注意:HMR在webpack官方给出了接口用于加载对应模块时 执行的业务逻辑进行局部刷新提高体验度
七、安装webpack插件webpack.optimize.uglifyJsPlugin()对打包的文件进行压缩
在module节点里加入以上 代码 可以 监控文件大小
var path=require('path');
var webpack=require('webpack');
module.exports={
entry: {
bundle1: './main1.js', //入口1
bundle2: './main2.js'//入口2
},
output: {
path:__dirname, //(如果指定路径必须是绝对路径)
path:path.join(_dirname,'dist') ,
publicPath:'/dist/' //在对应的html中引入 dist 目录下对应的打包文件
filename: '[name].bundle.js' // [name]是一个变量,会自动替换成entry的key
},
devtool:'source-map', //便于debugger调试,否则在浏览器的source中看不到自己的代码都是webpack的代码不利于调试
plugins:[
new webpack.optimize.uglifyJsPlugin() //对所有打包的文件进行压缩
new babili-webpack-plugin(); 也可以对文件进行压缩
],
module:{
loaders:[
{test:/\.js$/,loader:'babel'},
{test:/\.css$/,loader:'style!css'} //匹配已 .css 为扩展名的文件
]
}
}
plugins:的值是一个数组,所有插件都通过npm install进行安装,然后在plugins数组中添加对应的插件配置。有三个插件需要提一下:
1. HtmlwebpackPlugin 这个插件可以把生成的css ,js 插入到html页中的head,body中,这对于加了hash值的输出很有用。这个功能也可以用gulp的insject插件做。不过既然用webpack了,就暂时忘了gulp吧。在具有相似功能的不同的工具之间切换,并不是一个好主意。不过html这款插件有一个小小的问题,它对html中的img不会像css中那样解析。造成dist目录下的html文件,img下的src报错。解决办法是添加html-withimg-loader这个插件。
1 2 3 4 |
{ test:/.html$/, loader:'html-withimg-loader?min=false' }, |
2. CommonsChunkPlugin 提取公共代码,这个不需要安装,webpack集成有。
1 2 3 4 5 |
new webpack.optimize.CommonsChunkPlugin({ name:'vendor', filename:'js/vendor.js', chunks:['chunk1','chunk2','chunk3']//不写为所有的chunk, }), |
chunk (块), webpack中另一个非常重要的概念,和entry对应。有三种情况:
2.1 如果entry通过字符串的方式指定的入口文件,那么chunk就是指入口文件,比如entry: './app.js'; 那么可以肯定chunk和'./app.js'一一对应。
2.2 如果是entry:['./app1.js','app2.js']那么chunk就是这两个文件之和。
* 以上chunk的[name]就是默认的"main".
2.3 如果是下面这种形式:
1 2 3 4 5 |
entry:{ index:'./index.js', error:'./error.js', vendor:['react','redux'] } |
那么就会产生多个chunk,[name]分别和index,error,vendor对应。
3. ExtractTextPlugin 这个插件就是开头提到的,从html中分离css的插件。npm install extract-text-webpack-plugin --save-dev
1 2 3 |
plugins: [ new ExtractTextPlugin("[name].css"), ] |
需要注意的是,如果在[name].css前面加了子路径,如css/[name].css 那么就要小心样式中的图片路径出错,特别是在没有指定publicPah的情况下。background:url(这个地方的图片默认是和chunk的输出路径同级的,如果指定了publicPath,则以publicPath代替,不存在这个问题),但是由于我们人为的指定了打包后的样式放在css/目录下,而图片默认还在原来的目录,这就导致css中引用的图片路径失效。看下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var ExtractTextPlugin = require("extract-text-webpack-plugin"); module.exports = { entry: ['./main.js','./icon.css'], output: { path:'./dist', //publicPath:'./dist/', filename: 'bundle_[hash:8].js' }, module: { loaders:[ { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'}, { test: /\.css$/, loader: ExtractTextPlugin.extract('style', 'css') }, ] }, plugins: [ new ExtractTextPlugin("css/[name].css"), ] }; |
从图上看到,添了css子目录之后,样式中的图片,还是原来的路径,而图片并不存在css目录下。
解决办法,要么不加子目录,保持样式文件和图片文件始终在同一层级,要么添加publicPath,相当于使用绝对路径。由于这个是在webpack.config.js中配置的,要更换也很容易。
看起来一切都很美好,可是当我们的html中也用了img标签的时候,问题就来了,还记得html-withimg-loader这个插件吗?它际实上也是调用的url-loader,所以,它最终的图片输出路径也样受publicPath的影响。考虑一下这样的目录结构:
样式文件是位于css子目录,而html则是和图片保持同级的。样式中的图片需要指定为"../",而html中的图片需要指定成"./",这在同一个publicPath中,显示是冲突的。这时就需要权衡一下,要么所有的文件都堆在根目录下,要么html中的图片用别的插件进行处理。总之,不能让这种相冲突的情况发生。
最后再简单说一下webpack.config.js 中的 resolve;
1 2 3 |
resolve: { extensions: ['', '.js', '.vue', '.json'] }, |
通常我们都知道通过配置这个属性下的extensions,可以省略扩展名,似乎没有什么可以介绍的,直到有一次我在项目中通过npm install vue --save 安装了vue ,然后我在代码中用import Vue from 'vue' 导入了vue。到这一步都是正常的,可是当我打算进一步使用vue的时候,代码就报错了,然后去查官方文档(出错的时候不要惊慌,大多数情况都会在官方找到解决的办法,比如github中的issue等)。官方介绍如下:
“ There are two builds available, the standalone build and the runtime-only build. The difference being that the former includes the template compiler and the latter does not.By default, the NPM package exports the runtime-only build. To use the standalone build, add the following alias to your Webpack config:”
1 2 3 4 5 |
resolve: { alias: { 'vue$': 'vue/dist/vue.common.js' } }, |
大意是说:vue有两种构建方式,独立构建和运行构建。它们的区别在于前者包含模板编译器而后者不包含。而默认 NPM 包导出的是 运行时 构建。为了使用独立构建,要在 webpack 配置中添加下面的别名:添加alias:{'vue$':'vue/dist/vue.common.js'}. 这个别名,同样适用于jquery,zepto这些库。
对于vue来说,如果不用别名,也可以从node_modules/vue/dist/复制vue.common.js到开发目录下,比如./src/vue下面,然后像普通的js文件一样引用, import vue from './src/js/vue.common.js' 这也是可以的。只是和使用别名相比,显的很lower
八、优化打包速度 noParse
它的值是个数组,每个元素都是一个正则表达式;
也可以在exclude下边添加 include属性 值也是数组里边是正则表达式(正则写需要babel转换的文件,也就是用es6写的js)
1. exclude是排除的目录,比如node_modules中的文件,通常都是编译好的js,可以直接加载,因此为了优化打包速度,可以排除。作为优化手 段它不是必须的。
2. loader: 加载器的名称,每一个加载器都有属于它自己的用法,具体要参考官方说明。
3. query: 传递给加载器的附加参数或配置信息,有些也可以通过在根目录下生成特殊的文件来单独配置,比如.babelrc
test: /\.js[x]?$/,
exclude: /node_modules/,
loader: 'babel-loader',
query:{
presets:['react','es2015']
}
九、ESlint实现代码规范自动化测试
目的:提高个人代码的质量,提高团队编码风格的统一性,可用性的保障,以及性能优化(先不详细写了)
- 首先在项目下安装 npm install eslint --save-dev
然后在 package.json 文件 scripts中配置eslint
app/ 代表编译app目录下的文件
webpack.*.js --cahe 对webpack.*.js相关文件进行缓存然后在配置.eslintrc.js 如下图 不懂的配置规则请移步到官网 https://eslint.org/docs/rules/
然后在 命令行中 执行 npm run eslint 就可以对代码进行编辑检测,但是有错误总是手动去改成就会浪费人工时间,这时在1.4 之后的版本 使用 npm run eslint -- --fix 可以自动帮助我们进行修正;
例如 :代码结尾 不加 “分号” ,字符串用的“双引号“而不是“单引号“ 等等问题都会自动修正提高代码质量;
2:在webpack中配置 eslint
首先 安装 npm install eslint-loader --save-dev
之后再webpack.config.js中配置 eslint-loader
module:{
rules:[
{
test:/\.js$/,
enfore:'pre',
loader:'eslint-loader',
options:{
emitWarning:true
},
},
]}
然后在运行之前配置好的 npm run start会发现跟之前不同的是webpack在编译之前会先对代码进行检测;
另附(根据个人喜好选择):
可以配置在浏览器中显示 检测出来的错误 或者 警告 便于调试
在之前 webpack.config.js 文件中 配置好的devServer中添加配置项 overlay
十、分离项目打包代码和组件代码 (重点)
- 如何分离
然后 npm run build 对项目进行打包
会发现多了一个vendor.js 说明已把app.js进行了分离 大小也从175KB 变成 45.1KB
以上的图 左边是app.js 右边是vendor.js 他们是存在着公共部分代码的 如 A 和 下边节点B C
所以进行压缩合并配置 如下: - 对公共部分代码进行压缩合并
在webpack.config.js文件中 如果没导先导 webpack const webpack=require('webpack');
然后在plugins 节点下配置 下图标注的部分这个插件是webpack自带的;
合并完之后app 和 vendor 公共部分进行了合并 如 下图
然后在进行 npm run build 打包
会发现上次分离之后打包app文件的45KB 在进行合并打包之后
变成了 613JB vendor.js变成46KB 再一次进行了优化 - 需要注意的是 分离出来的文件vendor.js 要在html 引入进去并且 放在 bundle.js /app.js上边
十一、使用可视化图表进行统计分析打包过程 (自选)
目的:对后期性能优化做准备,方便找出某些组件之间的依赖关系以及大小,监控编译所占用的时间等等;
然后运行 npm run stats 回生成 stats.json
这里有可视化图表相关内容
http://blog.parryqiu.com/2017/06/16/webpack2-Statistics/