基本思路
- webpack基本配置:entry、output、loader、plugin。需要注意不同类型文件输出路径、引用路径、常用的loader、常用的plugin。
- webpack性能优化:打包速度及打包输出文件体积优化
- 深入理解webpack-dev-server文件系统
引入第三方包
const path = require('path');
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');//压缩js文件
const HtmlWebpackPlugin = require('html-webpack-plugin');
/* css相关 */
const MiniCssExtractPlugin = require("mini-css-extract-plugin");//提取css到单独文件的插件
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');//压缩css插件
const autoprefixer = require('autoprefixer');
const flexbugs = require("postcss-flexbugs-fixes");
const PurifyCssPlugin = require('purifycss-webpack');
const glob=require("glob-all");
/* 多进程loader */
const HappyPack = require('happypack');
const happythreadPool=HappyPack.ThreadPool({size:5});
webpack基础配置
entry:"path"/["path"]/{key:path}
output:{
path:"", //输出路径
filename:"", //entry输出文件名,[name]为entry.key
chunkFilename:"", //除entry外的单独输出js文件输出名,[name]为require.ensure第三个参数或import注释参数`import(/* webpackChunkName:"wac/requireJs/MainPageChild" */ '../page/MainPageChild.vue').then((module) => {})`
publicPath:"/", //静态文件引用路径,最好为根路径【不然有意想不到的问题,特别在管理静态文件时】,缺省值为相对于html的引用路径。
}
less文件处理及css文件优化【含压缩与css tree shaking】
执行步骤:
将less转为css文件【less、less-loader】、将css代码处理为兼容性好的css代码【postcss-loader+autoprefixer加前缀,将下一代css处理为兼容性好的文件】、加载css文件【css-loader】、引入见下两种方案:
1-1-1:将css代码插入到style标签中【style-loader】
1-1-2:提出单独css文件【mini-css-extract-plugin】、删除没有用css代码【puritycss-webpack、purify-css】、压缩css文件【optimize-css-assets-webpack-plugin】
代码展示:将css文件输出为chunk
module: {
rules: [{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,//'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [
flexbugs,
autoprefixer({
overrideBrowserslist: ["last 6 versions", "android >= 4.0", "ios >= 5.0", ">1%", "Firefox ESR", "not ie < 9"]
})
]
}
},
'less-loader?sourceMap'
]
}
plugins:{
new MiniCssExtractPlugin({
filename: "[name]/css/[name].css",
chunkFilename: "[id].css"
}),
/* css tree shaking,一定要在mini-css-extract-plugin后使用 */
new PurifyCssPlugin({
paths: glob.sync(_purifyCssArr) // glob.sync([path.resolve('./src/page/*.vue')]),安装glob-all,_purifyCssArr为需要检测css引用的文件,若某css在这些文件中都未使用则删除。
}),
/* 压缩css文件 */
new OptimizeCssAssetsPlugin()
}
jsx?文件处理
安装babel-loader
module: {
rules: [{
test: /\.jsx?$/,
use: [
'babel-loader'
],
exclude: /node_modules/
}]
}
png、jpe?g、svg、gif文件处理
url-loader与file-loader都可以加载任何文件。url-loader依赖于file-loader,当文件大小小于上限则可以返回一个 DataURL,否则则使用file-loader加载文件
module: {
rules: [{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
outputPath:'static/images/',
/* publicPath:'',// 配置不同静态资源存不同CDN时使用 */
name: "[name].[hash:7].[ext]"
}
}]
}
ttf、eot、woff2?文件处理
安装url-loader与file-loader
module: {
rules: [{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
outputPath:'static/images/',
/* publicPath:'',// 配置不同静态资源存不同CDN时使用 */
name: "[name].[hash:7].[ext]"
}
}]
}
vue文件处理
vue-loader
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader'
}]
}
js文件优化【提公共模块async、node_modules、公共文件】、压缩
plugins:[
new ParallelUglifyPlugin({
uglifyJS: {
warnings: false, // 在UglifyJs删除没有用到的代码时不输出警告
output: {
beautify: false, //不需要格式化
comments: true //不保留注释
},
compress: {
drop_console: true, // 删除所有的 `console` 语句,可以兼容ie浏览器
collapse_vars: true, // 内嵌定义了但是只用到一次的变量
reduce_vars: true // 提取出出现多次但是没有定义成变量去引用的静态值
}
}
})
,
optimization: {
splitChunks: { //js公共模块代码提取
chunks: 'all', //async、all、initial
cacheGroups: { // js公共模块单独提出文件匹配规则,cacheGroups会覆盖外层的属性值
// 公共模块抽离
commons: { //未有test,所以匹配所有公共模块
minChunks: 2, //抽离公共代码时,这个代码块最小被引用的次数
name:"static/commonJs/commons",
minSize: 0, // This is example is too small to create commons chunks
priority:1 //优先级低
},
// 第三方包抽离
vendors:{
test:(module)=>{
return /node_modules/.test(module.context);
},
minChunks:2,
name:'static/commonJs/vendors',
priority:10 //优先级高,先抽离公共此块的模块,再抽离下一个级别的模块
}
}
}
}
html-webpack-plugin与add-asset-html-webpack-plugin
html-webpack-plugin用于html关联webpack打包文件,可以配置模板,灵活的配置chunk。add-asset-html-webpack-plugin为引入外部文件到html中
plugins:[
new HtmlWebpackPlugin({
filename: "", //输出html文件
template: "", //模板html
chunks:[chunkName], //默认值为"all",将打包出的js文件自动引入到html中,输出js文件chunkName,chunkName为optimization.splitChunks.name
minify:true
})
],
chunk输出与引用路径总结
chunk【entry js,非entry js、css、image】输出路径:output.path+chunkNamechunk引用路径:output.publicPath+chunkName【默认情况】或者module.rule.options.publicPath【优先级高于前者】——publicPath最好不用相对路径。
输出文件路径key:entry输出文件路径key为path,除此之外输出文件路径的key都为outputPath
- entry js chunkName:output.filename
- not entry js chunkName:output.chunkFilename,其中output.chunkFilename:"[name]",[name]=require.ensure()第三个参数
- css文件chunkName:miniCsExtractPlugin.filename
- html文件chunkName:htmlWebpackPlugin.filename
- image chunkName:module.rule.options.outputPath+module.rule.options.name
webpack性能
- webpack构建速度优化:
- DllPlugin减少基础模块编译次数:
原理:1、添加webpack_dll.config.js,打包第三方抽离出来的单独包,在webpack_dll.config.js中使用new DllPlugin()生成模块对应关系manifest.json文件。2、在主webpack打包文件中,使用new DllReferencePlugin()添加DllPlugin生成manifest.json文件,DllReferencePlugin去 manifest.json 文件读取 name 字段的值,把值的内容作为在从全局变量中获取动态链接库中内容时的全局变量名,因此:在 webpack_dll.config.js 文件中,DllPlugin 中的 name 参数必须和 output.library 中保持一致。3、由于主webpack打包文件不会打包第三方抽离出来的代码,所以使用add-asset-html-webpack-plugin插件,将webpack_dll.config.js中生成的js插入在html中。
> 注意:使用DllPlugin,不能指定resolve.modules、resolve.mainFields等寻找node_modules中文件路径,这样会使webpack_dll.config.js生成的[name].mainfest.json文件node_modules文件的对应关系失效。
// webpack_dll.config.js文件
const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');
module.exports = {
context:__dirname,
entry: {
vue: ['vue/dist/vue.esm.js', 'vue', 'vue-router']
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, 'dll'),
library: '_dll_[name]', //dll的全局变量名
},
plugins: [
new DllPlugin({
context:__dirname,
name: '_dll_[name]', //[name].manifest.json的name值,与library名字一致
path: path.join(__dirname, 'dll', '[name].manifest.json')//描述生成的manifest文件
})
]
};
// 主webpack文件
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
const manifest = require("./dll/vue.manifest.json"); // 加载webpack_dll.config.js生成的[name].mainfest.json文件
plugins:[
new HtmlWebpackPlugin(),
new DllReferencePlugin({
context: __dirname,
manifest
}),
new AddAssetHtmlPlugin({
filepath: path.resolve(__dirname, './dll/*.dll.js'),
outputPath: "static/commonJs",// 引用.dll.js文件输出地址
publicPath: "/static/commonJs",// 访问.dll.js文件的基础路径
includeSourcemap: false
}),
]
- 依赖模块路径解析优化:
resolve:resolve.extensions、resolve.alias、resolve.modules、resolve.mainField
module:module.noParse、module.rule.include、module.rule.exclude
- 多进程转换:happypack
const HappyPack = require('happypack'); //开启多进程Loader转换
const happythreadPool=HappyPack.ThreadPool({size:5}); // 公用进程池
module:{
rules: [{
test: /\.es6?$/,
use: 'happypack/loader?id=babelES6'
}
]
}
plugins:[
new HappyPack({
id:"babelES6",
loaders:["babel-loader"],
threadPool:happythreadPool
})
]
- 多进程压缩js代码:webpack-parallel-uglify-plugin
plugins:[
new ParallelUglifyPlugin({
uglifyJS: {
warnings: false, // 在UglifyJs删除没有用到的代码时不输出警告
output: {
beautify: false, //不需要格式化
comments: true //不保留注释
},
compress: {
drop_console: true, // 删除所有的 `console` 语句,可以兼容ie浏览器
collapse_vars: true, // 内嵌定义了但是只用到一次的变量
reduce_vars: true // 提取出出现多次但是没有定义成变量去引用的静态值
}
}
})
]
-
压缩输出文件
css文件:optimize-css-assets-webpack-plugin、purifycss-webpack(删除没有用的css代码)。
使用purifycss-webpack:
new PurifyCssPlugin({
paths:glob.sync([path.resolve('./src/page/*.vue')])
})
js文件:
> 使用uglify-webpack-plugin或webpack-parallel-uglify-plugin(开启多进程使用Uglify压缩js代码)压缩js文件。
使用webpack-parallel-uglify-plugin:
new ParallelUglifyPlugin({
uglifyJS: {
warnings: false, // 在UglifyJs删除没有用到的代码时不输出警告
output: {
beautify: false, //不需要格式化
comments: true //不保留注释
},
compress: {
drop_console: true, // 删除所有的 `console` 语句,可以兼容ie浏览器
collapse_vars: true, // 内嵌定义了但是只用到一次的变量
reduce_vars: true // 提取出出现多次但是没有定义成变量去引用的静态值
}
}
})
-
拆分公共模块:
(首屏加载所需的node_modules为一个chunk,子页面单独用的node_modules为一个chunk)
-
不同类型静态资源配置不同CDN:
module.rule.options.publicPath设置不同类型静态文件引用路径
webpack-dev-server读写文件原理
将文件写入内存,写入内存中文件为Buffer格式。。读取通过memory-fs暴露方法读取文件。读取内存文件和往内存中写入文件都在webpack-dev-middleware插件中暴露出方法,一般读取文件、写入文件方法在context.fs对象中,context对象操作内存文件方法,由另一个文件中setFs扩充。