Netherspite

构建工具的作用:

  • 转换ES6语法、JSX
  • CSS前缀补全、预处理器
  • 压缩混淆
  • 图片压缩

构建的演变:

ant+YUI Tool -> grunt -> fis3/gulp -> rollup/webpack/parcel

webpack核心概念

entry

entry用来指定webpack的打包入口。webpack会根据模块依赖关系构建依赖图,entry是图的唯一入口。entry是一个字符串(单入口)或对象(多入口)。

module.exports = {
entry: './path/to/my/entry/file.js'//单入口
}
module.exports = {
entry: {
app: './src/app.js',
adminApp: './src/adminApp.js'//多入口
}
}

多入口

利用glob.sync动态获取entry和设置html-webpack-plugin数量。

const glob = require("glob");
const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'));

output

output将编译后的文件输出到指定目录。[name]是占位符,对应多入口的entry键值对的key。

module.exports = {
output: {
filename: '[name].js',
path: __dirname + '/dist'
}
}

output占位符

占位符名称 含义
[ext]
[name] 文件名
[path] 文件的相对路径
[folder] 文件所在的文件夹
[contenthash] 文件的内容hash,默认是md5生成
[hash] 文件内容的hash,默认是md5生成
[emoji] 一个随机的指代文件内容的emoji
module.exports = {
module: {
rules: [{
test: /\.(png|jpg|gif|jpeg)$/,
use: [{
loader: 'file-loader',
options: {
name: 'img/[name][hash:8].[ext]'
}
}]
}]
}
}

loaders

webpack开箱即用只支持JS和JSON两种文件类型,通过loaders去支持其他文件类型并把它们转化成有效的模块,并添加到依赖图中。loader本身是一个函数,接受源文件作为参数,返回转换的结果。

常见的loaders

名称 描述
babel-loader 转换ES6、ES7等JS新特性语法
css-loader 支持.css文件的加载和解析
less-loader 将less文件转换成css
ts-loader 将TS转换成JS
file-loader 进行图片、字体等的打包
raw-loader 将文件以字符串的形式导入
thread-loader 多进程打包JS和CSS

loaders的用法

module字段的rules字符来制定,其是一个对象数组,每个对象有test和use两个属性,test指定匹配规则,use指定使用的loader名称。

module.exports = {
module: {
rules: [
{test: /\.txt$/, use: 'raw-loader'}
]
}
}

babel-loader

babel的配置文件是.babelrc。presets和plugins增加ES6的babel preset配置。

//webpack.config.js
module.exports = {
module: {
rules: [
{test: /\.js$/, use: 'babel-loader'}
]
}
}
//.babelrc
{
presets: [
"@babel/preset-env",
"@babel/preset-react" //增加react的解析
],
plugins: "@babel/proposal-class-properties"
}

css-loader

css-loader加载.css文件,并且转换成CommonJS对象,style-loader将样式通过<style>标签插入到head中。loader调用是链式调用的,且调用顺序是从右到左的,所以下方先执行css-loader,再执行style-loader。

//webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: [
"style-loader",
"css-loader"
]
}, {
test: /\.less$/,
use: [
"style-loader",
"css-loader",
"less-loader"
]
}]
}
}

file-loader

file-loader解析图片、字体等文件。

module.exports = {
module: {
rules: [{
test: /\.(png|jpg|gif|jpeg)$/,
use: "file-loader"
}, {
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: 'file-loader'
}]
}
}

url-loader

除了file-loader之外,url-loader也可以处理图片和字体,可以设置较小资源自动base64。url-loader内部也是使用了file-loader。不超过options的limit字节大小的图片会直接base64合入所在js文件。

module.exports = {
module: {
rules: [{
test: /\.(png|jpg|gif|jpeg)$/,
use: [{
loader: 'url-loader',
options: {
limit: 10240//limit单位是字节
}
}]
}]
}
}

postcss-loader

postcss-loader配合autoprefixer可以针对不同的浏览器版本为CSS加上相应的前缀。

module.exports = {
module: {
rules: [{
test: /\.less$/,
use: [{
loader: 'postcss-loader',
options: {
plugins: () => [
require('autoprefixer')({
overrideBrowserslist: ['last 2 version', '>1%', 'ios 7']
})
]
}
}]
}]
}
}

px2rem-loader

CSS媒体查询实现响应式布局的缺点是需要写多套适配样式代码,rem就可以实现自适应CSS的开发。使用px2rem-loader会在页面渲染时计算根元素的font-size值。

module.exports = {
module: {
rules: [{
test: /\.less$/,
use: [{
loader: 'px2rem-loader',
options: {
remUnit: 75,//rem相对于px的转换单位,相当于1rem=75px
remPrecision: 8//转换成rem时小数点后的位数
}
}]
}]
}
}

raw-loader

raw-loader可以内联html、js等资源,资源内联可以对页面框架作初始化、上报相关打点。CSS内联避免页面闪动。可以减少HTTP网络请求数:比如小图片或字体。

<!DOCTYPE html>
<html lang="en">

<head>
${ require('raw-loader!./meta.html') }<!--引入meta.html内联 -->
<title>Document</title>
<script>${ require('raw-loader!babel-loader!../node_modules/lib-flexible/flexible.js') }//引入脚本文件内联</script>
</head>

<body>

</body>

</html>

plugins

plugins用于bundle文件的优化,资源管理和环境变量注入,作用于整个构建过程。

常见的plugins

名称 描述
CommonsChunkPlugin 将chunks相同的模块代码提取成公共js
CleanWebpackPlugin 清理构建目录
ExtractTextWebpackPlugin 将CSS从bundle文件里提取成一个独立的CSS文件
CopyWebpackPlugin 将文件或者文件夹拷贝到构建的输出目录
HtmlWebpackPlugin 创建HTML文件去承载输出的bundle
UglifyjsWebpackPlugin 压缩JS
ZipWebpackPlugin 将打包出的资源生成一个zip包

MiniCssExtractPlugin

MiniCssExtractPlugin可以提取css为一个单独的文件。其余style-loader的功能互斥。用法如下。

'use strict';

const MiniCSSExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
module: {
rules: [{
test: /\.css$/,
use: [
//"style-loader", //与提取css的功能互斥
MiniCSSExtractPlugin.loader,
"css-loader"
]
}, {
test: /\.less$/,
use: [
//"style-loader",
MiniCSSExtractPlugin.loader,
"css-loader",
"less-loader"
]
}]
},
plugins:[
new MiniCSSExtractPlugin({
filename: `[name]_[contenthash:8].css`
})
]
};

SplitChunksPlugin

webpack4内置的,替代CommonsChunkPlugin插件。

chunks参数有三种:

  • async:异步引入的库进行分离(默认)
  • initial:同步引入的库进行分离
  • all:所有引入的库进行分离
module.exports = {
opimization: {
splitChunks: {
chunks: 'async',
minSize: 30000, //公共包最小的字节数
maxSize: 0,
minChunks: 1, //几个页面同时使用才提取,比如为2的话就两个页面用才会提取
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~', //
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
}
}
}
}
}

mode

mode用来指定当前的构建环境:production、development和none。设置mode可以使用webpack内置的函数,默认是production。

选项 描述
development 设置process.env.NODE_ENV的值为development。开启NamedChunksPlugin和NamedModulesPlugin
production 设置process.env.NODE_ENV的值为production。开启FlagDependencyUsagePlugin等
none 不开启任何优化选项

webpack进阶

监听

文件监听是在发现源码发生变化时,自动重新构建出新的输出文件。启动webpack时,带上--watch参数或者在webpack.config.js中设置watch: true。该模式的缺陷是需手动刷新浏览器。

其监听的原理是轮询判断文件的最后编辑时间是否变化。某个文件发生了变化,并不会直接告诉监听者,而是先缓存起来,等aggregateTimeout。

module.exports = {
watch: true, //默认为false,不开启
watchOptions: { //只有开启监听模式才有意义
ignored: /node_modules/, //默认为空,不监听的文件或文件夹,支持正则
aggregateTimeout: 300, //监听到变化会等待之后去执行,默认300ms
poll: 1000 //每秒轮询的次数,默认每秒问1000次
}
}

文件指纹

打包后输出的文件名的后缀就是文件指纹,通常应用于版本管理,一般采用chunkhash。css文件常用contenthash。使用方法是设置output的filename,使用[chunkhash]来指定。

module.exports = {
output: {
filename: '[name][chunckhash:8].js',
path: __dirname + '/dist'
},
plugins: [
new MiniCssExtractPlugin({
filename: `[name][contenthash:8].css`
});
]
}
  • hash,和整个项目的构建相关,只要项目文件有修改,整个项目构建的hash值就会改变
  • chunkhash,和webpack打包的chunk有关,不同的entry会生成不同的chunkhash,不能与热更新一起使用
  • contenthash,根据文件内容来定义hash,文件内容不变则contenthash不变

热更新

  • 使用webpack-dev-server,其不刷新浏览器,不输出文件,而是放在内存中,使用HotModuleReplacementPlugin插件。Webpack Compiler将js编译成Bundle,HMR Server将热更新的文件输出给HMR Runtime,Bundle Server提供文件在浏览器的访问,HMR Runtime会被注入到浏览器,更新文件的变化,bundle.js即构建输出的文件。

  • 使用webpack-dev-middleware,将webpack输出的文件传输给服务器,适用于灵活的定制场景。

代码压缩

包括HTML、CSS和JS文件的压缩。

  • JS:webpack内置了uglifyjs-webpack-plugin对JS进行了压缩。

  • CSS:使用optimize-css-assets-webpack-plugin以及cssnano(预处理器)对CSS文件进行压缩

    module.exports = {
    plugins: [
    new OptimizeCSSAssetsWebpackPlugin({
    assetNameRegExp: /\.css$/g,
    cssProcessor: require('cssnano')
    })
    ]
    }
  • HTML:修改html-webpack-plugin,设置压缩参数minify。

    module.exports = {
    plugins: [
    new HTMLWebpackPlugin({
    template: path.join(__dirname, 'src/search.html'), //模板所在位置
    filename: 'search.html', //打包出来的文件名称
    chunks: ['search'], //HTML文件使用的chunk
    inject: true, //为true的话chunk中的js和css会自动注入到HTML中
    minify: {
    html5: true,
    collapseWhitespace: true,
    preserveLineBreaks: false,
    minifyCSS: true,
    minifyJS: true,
    removeComments: false
    }
    })
    ]
    }

source map

通过source map可以定位到源代码,开发环境开启,线上环境关闭,线上排查问题的时候可以将sourcemap上传到错误监控系统。

关键字

  • eval:使用eval包裹模块代码

  • source map:产生.map文件

  • cheap:不包含列信息

  • inline:将.map作为DataUrl嵌入,不生成单独.map文件

  • module:包含loader的sourcemap

引入方法:

module.exports = {
devtool: 'source-map'
}

Tree Shaking

一个模块可能有多个方法,只要其中某个方法使用到了,则整个文件都会被打包到bundle中去,tree shaking就是只把用到的方法打入bundle,没用到的方法会在uglify阶段被擦除。必须是ES6的语法,CJS(require)的方式不支持。

使用:webpack默认支持,在.babelrc里设置modules:false即可。生产环境默认开启。

Dead Code Elimination的特点:

  • 代码不会被执行,不可到达
  • 代码执行的结果不会被用到
  • 代码只会影响死变量(只写不读)

Scope Hoisting

构建后的代码存在大量函数闭包包裹代码,导致体积增大(模块越多越明显)。运行代码时创建的函数作用域变多,内存开销变大。

被webpack转换后的模块会带上一层包裹,import会被转换成__webpack_require。打包出来的是一个IIFE,modules是一个数组,每一项是一个模块初始化函数,__webpack_require用来加载模块,返回module.exports,通过WEBPACK_REQUIRE_METHOD(0)启动程序。

(function(modules) {
var installedModules = {};

function __webpack_require(moduleId) {
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
__webpack_require(0);
}
})([
(function (module, __webpack_exports__, __webpack_require__) {
...
}),
(function (module, __webpack_exports__, __webpack_require__) {
...
}),
(function (module, __webpack_exports__, __webpack_require__) {
...
})
])

Scope Hoisting原理

将所有模块代码按照引用顺序存放在一个函数作用域里,然后适当的重命名一些变量以防止命名冲突。通过scope hoisting可以减少函数声明代码和内存开销。production默认开启(ModuleConcatenationPlugin插件),必须使用ES6语法。


 上一页

HTML Web

 评论