Webpack
Webpack本质上是一种前端资源编译、打包工具
webapck的核心流程
- 入口处理: 从
entry入口文件开始,启动编译流程 - 依赖解析: 从
entry入口文件开始,根据require或import等语句找到依赖资源。 - 资源解析: 根据
module配置,调用资源转译器,将png、css等非标准JS资源转译为JS资源。 - 资源合并打包: 将转译后的资源内容合并打包为可直接在浏览器中运行的JS文件。
安装及简单使用
npm i webpack webpack-cli -D
安装过程如上述命令,然后在根目录下手动创建一个webpack.config.js然后只需要三个属性的配置即可完成wepack的运行。
module.exports = {
entry: './src/index.js',
output: {
filename: '[name].js',
path: path.resolve(__dirname, './dist')
},
mode: 'production'
}
entry定义了打包入口,即指定了入口文件;output定义了输出文件的路径和文件名;mode定义了模式,production为生产模式,development为开发模式,这两个模式还是会有一些区别的:主要是两种模式会自动使用一些插件继续配置和优化。
-
development打包后,一些没有依赖的方法、变量、文件会保留,production则会移除。
-
production打包后,代码会进行压缩,比development的文件小;会启用UglifyJsPlugin插件(移除未使用的内容和文件压缩)。
npx webpack
执行上述命令,即可执行webpack进行打包。在此使用npx的目的主要是其可以执行依赖包中的命令,安装完成自动运行,让npm包中的命令行工具和其他可执行文件在使用上变得更加简单。
处理CSS
安装依赖
npm i css-loader style-loader -D
添加module处理css文件
module.exports = {
entry: './src/index.js',
output: {
filename: '[name].js',
path: path.resolve(__dirname, './dist')
},
module: {
rules: [
{
test: /\.css$/, // 匹配css结尾的文件
use: ['style-loader', 'css-loader'] // 使用这两个loader解析css
}
]
},
mode: 'production'
}
对于入口文件index.js来说,我们只需要引入相关css文件即可,以下两种方式均可。
import 'index.css'
import styles from 'index.css'
Loader
Loader的作用
Webpack Loader最核心的功能是实现了内容转译器,将各式各样的网络资源转为标准的JS内容。
Loader 的结构
代码层面,Loader是一个函数,结构如下:
module.exports = function(source, sourceMap?, data?) {
// source 为 loader 的输入,可能是文件内容,也可能是上一个 loader 处理结果
return source;
};
Loader函数接收三个参数,分别是:
- source:资源输入,对于第一个执行的loader,source为资源文件的内容,对于非第一个执行的loader,为前一个loader的执行结果。
- sourceMap:可选参数,代码中的sourcemap结构
- data: 可选参数,为其他需要在Loader链中传递的信息,比如posthtml、posthtml-loader就会通过这个参数传递参数的AST对象。
Loader实例
假设有个需求,我们需要对js文件中的变量或常量a进行相乘操作,并打印出来。与之相乘的乘数由用户在配置这个Loader的时候自定义。
webpack.config.js代码如下:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'index.js',
path: path.resolve(__dirname, './dist')
},
module: {
rules: [
{
test: /\.js$/,
use: [{
// 需要使用path,为loader属性指定我们写的loader的路径
loader: path.resolve(__dirname, './src/loader.js'),
// 用户自定义的乘数,此处设置为2
options: {times: 2}
}],
}
]
},
mode: 'development'
}
其中,mode建议设置为development模式,否则,若为production模式,会自动进行代码简化,产生的结果不明显。本人一开始使用的就是production,结果产生的index.js文件为空,是因为我实现功能的时候,字符串拼接有问题,导致Loader函数中return后的结果不能被JS正常解析,优化后,所以为空了。
Loader函数如下:
module.exports = function (source, sourceMap, data) {
source = source.replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\2029")
// 获取用户设置的options对象
const options = this.query
// 获取用户设置的options对象中的times,也就是乘数
const times = options.times
// 返回实现功能的字符串
return `${source} console.log(a*${times})`
}
函数内容部分的第一行中,\u2028的字符为行分隔符,会被浏览器理解为换行,而在Javascript的字符串表达式中是不允许换行的,从而导致错误。同理,\u2029的字符为段分隔符,不处理也会报错。
src目录下的index.js代码如下:
const a = 1;
使用npx weboack命令运行后,打开dist目录下的index.js,即可查看结果。
当然,Loader可以实现的功能远不止如此,还可以支持链式执行和异步执行,以及normal和pitch两种模式。在此只是简单的介绍其使用方法。
将打包后的JS文件插入到指定的html文件中
安装依赖
npm i html-webpack-plugin -D
把html-webpack-plugin插件添加plguins中
module.exports = {
entry: './src/index.js',
output: {
filename: '[name].js',
path: path.resolve(__dirname, './dist')
},
// 核心代码
plugins: [
new HTMLWebpackPlugin({
// 指定html的路径
template: './src/index.html'
})
],
mode: 'production'
}
Plugin
Plugin的作用
Webpack 通过 Plugin 机制让其更加灵活。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
插件架构的精髓,就是对拓展开放,对修改封闭。
Plugin的结构
代码层面,Plugin是一个类,结构如下:
class BasicPlugin{
// 在构造函数中获取用户给该插件传入的配置
constructor(options){
}
// Webpack 会调用 BasicPlugin 实例的 apply 方法给插件实例传入 compiler 对象
apply(compiler){
compiler.plugin('compilation',function(compilation) {
})
}
}
// 导出 Plugin
module.exports = BasicPlugin;
使用方法
const BasicPlugin = require('./BasicPlugin.js');
module.export = {
...(此处省略很多代码)
plugins:[
new BasicPlugin(options),
]
...(此处省略很多代码)
}
Webpack 启动后,在读取配置的过程中会先执行 new BasicPlugin(options) 初始化一个 BasicPlugin 获得其实例。
在初始化 compiler 对象后,再调用 basicPlugin.apply(compiler) 给插件实例传入 compiler 对象。
插件实例在获取到 compiler 对象后,就可以通过 compiler.plugin(事件名称, 回调函数) 监听到 Webpack 广播出来的事件。
可以看得出来,Plugin的核心就是在合适的钩子中执行一些代码,以产生期望的结果。用户可以在webpack指定的钩子中(即用户自己指定一些函数的调用时机),利用webpack提供的上下文环境(一些可能用到的变量或函数等),满足用户的编程需求。
webpack提供的主要钩子及其发生顺序如下图所示。
需要说明的是,使用钩子时是可以进行异步编程和Promise编程的。