webpack4学习笔记三:webpack进阶
1、 自动清理构建目录产物
1.1 使用npm scripts清理构建目录
在scripts
配置里面加这个语句
rm -rf ./dist && webpack
1.2 自动清理构建目录
使用插件clean-webpack-plugin
,默认会删除output指定的输出目录
安装:npm install clean-webpack-plugin -D
使用:
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
plugins: [
new CleanWebpackPlugin()
]
2、 自动补齐css前缀
各个浏览器内核前缀一览:
trident: -ms Geko:-moz webkit: -webkit presto: -o
自动补齐CSS需要使用到:autoprefixer
、postcss-loader
;autoprefixer它是根据www.caniuse.com这个网站的规则来进行补齐的;postcss-loader有很多功能,包括自动补齐css前缀,css module等
安装: ` npm install autoprefixer postcss-loader -D`
使用:
module:{
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader', {
loader: 'postcss-loader',
options: {
plugins: () => [
require('autoprefixer')({
"overrideBrowserslist": [
"last 2 version",
"> 1%"
]
})
]
}
}]
}
]
}
3、 css自动做rem转换
使用:px2rem-loader
插件、淘宝的插件lib-flexible
, lib-flexible需要手动放到打包好的文件里面的index.html里面,需要手动写一个script,把源码复制放进去。手淘的这个库有个好处,它会比较方便的解决手机端的1px问题。考虑到兼容性的话选择这个,如果不考虑的话可以使用VW or VH
安装:npm install px2rem-loader -D
、npm install lib-flexible -S
配置:
module:{
rules: [
{
test: /.js$/,
use: 'babel-loader'
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => [
require('autoprefixer')({
"overrideBrowserslist": [
"last 2 version",
"> 1%"
]
})
]
}
},
{
loader: 'px2rem-loader',
options: {
remUnit: 75, // 1个rem=多少个像素
remPrecision: 8 //保留几位小数点
}
}
]
}
]
},
注意事项:
- 如果是使用
less-loader
的话,需要['px2rem-loader', 'less-loader']
,注意前后顺序。 - 如果不想转化,可以使用
/*no*/
语法来注释,font-size:12px; /*no*/
; - 会把第三方UI库的px也转了
- 适用于移动端,PC还是px好。
4、 资源内联
资源内联可以优化页面框架的初始化,css内联可以避免页面闪动,小图片或者字体内联可以减少HTTP网络请求。
HTML可以使用row-loader
, JS的话需要多用一个babel-loader
,用来解析ES6
安装使用
npm install raw-loader@0.5.1 -D
// 在index.html 里面
// 内联一个HTML文件
${ require('raw-loader!./meta.html') }
// 内联一个JS文件
<script> ${require('raw-loader!babel-loader!../../node_modules/lib-flexible/flexible.js')} </script>
CSS 内联的核心思路是:将页面打包过程的产生的所有 CSS 提取成一个独立的文件,然后将这个 CSS 文件内联进 HTML head 里面。这里需要借助 mini-css-extract-plugin 和 html-inline-css-webpack-plugin 来实现 CSS 的内联功能。
// webpack.config.js
const path = require('path');
module.exports = {
entry: {
index: './src/index.js',
search: './src/search.js'
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name]_[chunkhash:8].js'
},
mode: 'production',
plugins: [
new MiniCssExtractPlugin({
filename: '[name]_[contenthash:8].css'
}),
new HtmlWebpackPlugin(),
new HTMLInlineCSSWebpackPlugin()
]
};
5、 多页面应用打包
每一次页面跳转的说话,后台服务器都会返回一个新的HTML文档,这种类型的网站也就是多页网站,也叫做多页面应用。
5.1 多页面打包基本思路
在webpack.config.js
中设置entry
,一个页面对应一个html-webpack-plugin. 缺点是每次新增或删除页面都需要修改webpack
配置。
entry:{
app: './src/index.js',
admin: './src/admin.js'
},
上面的方法略微繁琐,我们根据程序思维来,动态获取entry和设置html-webpack-plugin。动态设置需要用到一个库glob
,我们需要用到glob.sync
entry: glob.sync(path.join(__dirname, './src/*/index.js')),
我们需要约定好每个入口都是在src
文件下的xx
文件夹下的index.js
安装: npm install glob -D
调试:
'use strict'
const path = require("path");
const glob = require('glob');
const webpack = require("webpack");
const setMPA = () => {
const entry = {};
const htmlWebpackPlugins = [];
const entryFile = glob.sync(path.join(__dirname, './src/*/index.js'));
console.log(entryFile);
return {
entry,
htmlWebpackPlugins
}
}
// setMPA(); // 调用,查看打印出来的东西
/*
输出
[ 'D:/learn/学习/webpack/webpack多页面/src/index/index.js',
'D:/learn/学习/webpack/webpack多页面/src/main/index.js' ]
*/
module.exports = {
}
最终代码:
'use strict'
const path = require("path");
const glob = require('glob');
const webpack = require("webpack");
const setMPA = () => {
const entry = {};
const htmlWebpackPlugins = [];
// 使用glob匹配到所有的入口文件
const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'));
// 拿到所有的文件进行遍历
Object.keys(entryFiles).map(index => {
// 获取到每个文件
const entryFile = entryFiles[index];
// 获取到每个入口的名字,设置出口
const pageName = entryFile.match(/src\/(.*)\/index\.js/)[1];
entry[pageName] = entryFile;
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({
// 模板
template: path.join(__dirname, `src/${pageName}/index.html`),
// 指定打包的文件名字
filename: `${pageName}.html`,
chunks: [pageName],
inject: true,
minify:{
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false
}
})
)
})
return {
entry,
htmlWebpackPlugins
}
}
const {entry, htmlWebpackPlugins} = setMPA();
module.exports = {
entry: entry, // 入口文件
output: { // 导出的位置 文件夹的名字及文件的名字
path: path.join(__dirname, "dist"),
filename: "[name].js",
},
mode: "development", // 开发
module:{
rules: []
},
plugins: [
new webpack.HotModuleReplacementPlugin()
].concat(htmlWebpackPlugins)
}
运行:npm run build
查看dist
目录下面就有四个文件了 admin.js
,admin.html
,index.js
,index.html
;
6、 source map
Source map就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码,但是生产环境中不要使用,会暴露业务逻辑。配置了source-map
的项目按F12,查看源代码就可以看到业务逻辑,而不是打包混淆后的代码。
在webpack中可以直接配置:
module.exports = {
devtool: 'source-map'
}
可以配置的类型有下面的几种。
7、 提取页面公共资源
大的项目需要考虑性能优化,基础包、页面公共js文件提取是必备的手段,简单的可以库、插件可以通过直接script
CDN引入,使用webpack的话需要使用到一下插件。
基础库分离可以使用:html-webpack-externals-plugin
, 安装就不说了。
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
new HtmlWebpackExternalsPlugin({
externals: [
{
module: 'jquery', // 模块名字
entry: 'dist/jquery.min.js', // 入口,一般可能是本地文件或者CDN文件
global: 'jQuery', // 全局名字
},
// ------ 分离CDN -----------------
{
module: 'jquery',
entry: 'https://unpkg.com/jquery@3.2.1/dist/jquery.min.js',
global: 'jQuery'
},
],
})
分离完了需要在模板文件内使用script
引入,需要在<body></body>
里面。
有点不太好,我们还可以使用splitChunksPlugin
7.1 splitChunksPlugin
splitChunksPlugin是webpack4内置的插件。
module.exports = {
//...
optimization: {
splitChunks: {
// async:异步引入的库进行分离(默认), initial: 同步引入的库进行分离, all:所有引入的库进行分离(推荐)
chunks: 'async',
minSize: 30000, // 抽离的公共包最小的大小,单位字节
maxSize: 0, // 最大的大小
minChunks: 1, // 资源使用的次数(在多个页面使用到), 大于1, 最小使用次数
maxAsyncRequests: 5, // 并发请求的数量
maxInitialRequests: 3, // 入口文件做代码分割最多能分成3个js文件
automaticNameDelimiter: '~', // 文件生成时的连接符
automaticNameMaxLength: 30, // 自动自动命名最大长度
name: true, //让cacheGroups里设置的名字有效
cacheGroups: { //当打包同步代码时,上面的参数生效
vendors: {
test: /[\\/]node_modules[\\/]/, //检测引入的库是否在node_modlues目录下的
priority: -10, //值越大,优先级越高.模块先打包到优先级高的组里
filename: 'vendors.js'//把所有的库都打包到一个叫vendors.js的文件里
},
default: {
minChunks: 2, // 上面有
priority: -20, // 上面有
reuseExistingChunk: true //如果一个模块已经被打包过了,那么再打包时就忽略这个上模块
},
cacheGroups: {
commons: {
test: /(react|react-dom)/, // 匹配出需要分离的包
name: 'vendors',
chunks: 'all'
}
}
}
}
}
};
简单使用:不要和html-webpack-externals-plugin
一起使用
module.exports = {
// ...
optimization: {
splitChunks: {
cacheGroups: {
cacheGroups: {
commons: {
test: /(jquery)/,
name: 'vendors',
chunks: 'all'
}
}
}
}
}
}
代码抽离成功,使用的话需要注入到HtmlWebpackPlugin
插件里面去
new HtmlWebpackPlugin({
// 这个前后顺序是有关系的,chunks 决定了插入到 html 里面的 js 脚本的引用顺序。
chunks: ['vendors', pageName]
})
使用splitChunks
分离公共资源,打包common
module.exports = {
// ...
optimization: {
splitChunks: {
minSize: 0, // 最小0字节,代表只要引用就会打包
cacheGroups: {
commons: {
name: 'commons',
chunks: 'all',
minChunks: 2 // 最少引用的次数
}
}
}
}
}
使用依然需要注入到HtmlWebpackPlugin
插件里面去
8、 Tree Shaking(摇树优化)
未经优化的代码1个模块中可能有多个方法,只要其中的某个方法用到了,整个文件都会被打到bundle里面,tree shaking就是只把用到的方法打入到bundle,没有用到的方法会在uglify阶段被擦除掉。
tree shaking是webpack默认支持的,在.babelrc
里设置modules:false
即可,在mode: production
的情况下默认开启。 tree shaking必须是ES6的语法,CJS的方式不支持。
CJS: commonjs规范,比如Node.js的模块化就是采用commonjs
9、Scope Hoisting
webpack4比较重要的一个特性。未开启scope hoisting的话打包完成的bundle代码会有闭包,项目中大量的模块会带来大量的函数闭包包裹代码,导致体积增大,运行代码时创建的函数作用域变多,内存开销变大。
开启之后会做优化:将所有模块的代码按照引用顺序放到一个函数作用域里面,然后适当的重命名一些变量防止变量名冲突,可以减少函数声明代码和内存开销。Scope housting对模块的引用次数大于1次是不产生效果的,这个其实也很好理解,如果一个模块引用次数大于1次,那么这个模块的代码会被内联多次,从而增加了打包出来的js bundle的体积。
使用: webpack4里面,如果mode为production
默认开启,scope hoisting也必须是ES6语法,CJS不支持; 在webpack3里面需要加一行代码
plugins: [
nwe webpack.optimize.ModuleConcatenationPlugin()
]
10、代码分割
对于大的项目来说,首次便将所有代码都加载是不够有效实用的,特别是当你的某些代码是在某些特殊时候才会被用到,webpack的一个功能就是将代码库分割成chunks(语块),当代码运行到需要它们的时候再进行加载。
适用的场景:
- 抽离相同代码到同一个共享块。
- 脚本懒加载,使初始下载的代码更小。
可以用下面的两种方法:
使用CJS的方法: require.ensure
使用ES6:动态import,这个方式需要使用Babel转换,推荐用这个。
10.1 懒加载JS脚本——动态import
ES6普通import:import xx from ‘xx’ 这种是静态导入,在代码还没有执行的时候就已经导入了。
动态import: 根据逻辑进行按需加载。
安装babel插件: npm install @babel/plugin-syntax-dynamic-import --save-dev
配置: 在.babelrc
文件里
{
"presets": [
"@babel/preset-env"
],
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}
使用:当上面设置好之后,可以动态判断在某个事件里import
某个js文件,此处为test.js
, 返回的是一个promise对象,我们只需要直接.then
接着操作就好了。
someEvent(){
import('./test.js').then( () => {
// ....
})
};
原理: webpack会在编译阶段,把动态import
的文件抽离出来成为一个js文件,当import
的时候,会发送一个jsonp
的请求,请求该文件,然后执行处理,请求回来的是一个promist
对象。