浅学一下webpack
webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个依赖图,然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。
一、简单实践
- 初始化项目,并安装 webpack 与 webpack-cli
// 初始化项目
npm init -y
// 安装 webpack 和 webpack-cli
npm install webpack webpack-cli --save-dev
- 创建js文件,使用 exports 暴露方法
exports.sayHi = function(){
document.write("<h1>hello</h1>");
};
- 创建入口文件
var hello = require('./hello');
hello.sayHi();
- 创建webpack打包用的文件,webpack.config.js
// 引入 node核心模块 path
const path = require('path')
// 调用它的 .resolve() 方法
const _dirname = path.resolve();
console.log('前工作目录的绝对路径', _dirname);
module.exports = {
// webpack 默认模式是 production,打包后代码被压缩
// development 模式下,打包后的代码没有被压缩
mode: 'development',
// 项目打包的入口文件,从哪个文件打包
entry: './src/index.js',
// 打包后放在哪个文件夹下
output: {
// 打包后的文件名字
filename: 'main.js',
// 打包出的文件放在哪里(绝对路径)
path:path.resolve(_dirname, 'dist')
}
}
-
执行
webpack
命令,打包构建后就会生成dist文件夹下的main.js
文件。然后我们就可以在html中直接引入该js<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>webpack</title> </head> <body> <script src="../dist/main.js"></script> </body> </html>
二、Loader
webpack 只能处理JavaScript
和 JSON
文件,loader
能让 webpack 处理其他类型的文件,并将他们转换为有效模块。
- 安装相应
loader
npm install --save-dev css-loader
loader
在module
下的rules
中进行配置loader
从下到上、从右到左执行
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: true,
},
},
{ loader: 'sass-loader' },
],
},
],
},
};
// 对每个.css文件从 sass-loader 开始执行,然后继续执行 css-loader,最后以 style-loader 为结束。
常见的loader
-
css-loader
:会对@import
和url()
进行处理 -
style-loader
:将CSS注入到JavaScript中,通过DOM操作控制css -
sass-loader
:将SCSS/SASS代码转换成CSS -
postcss-loader
:处理css的loader,可以配合 autoprefixer 插件自动补齐 CSS3 前缀postcss-loader在css-loader和style-loader之前,在sass-loader或less-loader之后
-
file-loader
:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体) -
url-loader
:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值会交给 file-loader 处理,小于阈值时返回文件 base64 形式编码 (处理图片和字体) -
raw-loader
:加载文件原始内容(utf-8)- webpack5 内置了 asset 模块, 用来代替 file-loader & url-loader & raw-loader 处理静态资源
-
babel-loader
:把es6转换成es5 -
ts-loader
: 将 TypeScript 转换成 JavaScript -
eslint-loader
:通过 ESLint 检查 JavaScript 代码 -
tslint-loader
:通过 TSLint检查 TypeScript 代码 -
vue-loader
:加载 Vue.js 单文件组件
三、Plugin
插件可以用于执行比loader范围更广的任务。包括:打包优化,资源管理,注入环境变量。
webpack 插件是一个具有 apply
方法的 JavaScript 对象。
在 webpack.config.js
中配置:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); // 访问内置的插件
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
filename: 'my-first-webpack.bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: { ... },
plugins: [
new webpack.ProgressPlugin(),
new HtmlWebpackPlugin({ template: './src/index.html' }),
],
};
ProgressPlugin
用于自定义编译过程中的进度报告,HtmlWebpackPlugin
将生成一个 HTML 文件,并在其中使用 script
引入一个名为 my-first-webpack.bundle.js
的 JS 文件。
常见的plugin
html-webpack-plugin
:将为你生成一个HTML5文件(dist下的index.html),在body中使用script标签引入你所有webpack生成的bundle。progress-bar-webpack-plugin
:增加编译进度条ignore-plugin
:忽略部分文件mini-css-extract-plugin
: 分离样式文件,CSS 提取为独立文件,支持按需加载 (替代extract-text-webpack-plugin)
四、Loader 和 Plugin 的区别
loader
是文件加载器,能加载资源文件,并对这些文件进行处理,如编译、压缩等。最终一起打包到指定文件中。loader
执行顺序是反过来的,后续loader
接收前一个执行的loader
的返回值作为参数,最后执行的loader
会返回此模块的js源码。
loader
本身是一个函数,它的职责是单一的,只需要完成一种转化。
loader
在在 module.rules 中配置,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。
plugin
本质是插件,基于事件流框架tapable
,插件可以扩展webpack 的功能,它的功能比loader更加强大。
plugin
一旦注入到webpack
中后,它会在对应的生命周期函数里绑定一个事件函数,当webpack
的主程序执行到那个生命周期对应的处理工序时,plugin
绑定的事件就会触发。
Plugin
在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入。
五、webpack构建流程
-
初始化参数 - 从配置文件
webpack.config.js
和shell
语句中读取并合并参数,得到最终打包配置参数 -
开始编译 - 利用初始化的参数创建
complier
对象,加载所有配置的插件,然后调用complier.run
方法开始编译complier
对象:是webpack提供的一个全局的对象,这个对象上面挂载了一些在插件生命周期中会使用到的功能和属性,比如options、loader、plugin等。我们可以通过这个对象,在构建中获取webpack相关的数据。 -
编译模块 - 从入口文件开始,调用所有配置的
Loader
对模块进行处理,再找出该模块依赖的模块,递归处理,直到所有入口依赖的文件都经过了处理。 -
完成编译 - 编译完成后得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
-
输出资源 - 根据入口和模块的依赖关系,调用
compilation.seal
方法,组装成一个个包含多个模块的Chunk
,再把每个Chunk
转换成一个单独的文件加入到输出列表。chunk
(代码块):是一些模块 (module) 的封装单元。于 webpack 运行时的 seal 封包阶段生成,且直到资源构建阶段都会持续发生变化的代码块 -
写入文件 - 在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
六、代码分离
代码分离能够把代码分离到不同的bundle
中,然后再按需加载或并行加载。
代码分离可以用于获取更小的bundle
可以控制资源加载优先级,进而影响加载时间。
代码分离的方法
修改入口entry
webpack中配置两个入口
const path = require('path');
module.exports = {
entry: {
index: './src/index.js',
print: './src/print.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
打包后输出目录下就会有两个bundle
文件,达到了代码分离的效果
缺点:
- 如果这两个文件都引入了common模块,那么这两个文件就都会把common压缩进去,造成重复引用
- 不够灵活,不能动态的将核心代码逻辑拆分出来
防止重复
将公共模块抽离出来
entry: {
index: {
import: './src/index.js',
dependOn: 'shared'
},
print: {
import: './src/print.js',
dependOn: 'shared'
},
shared: './src/common.js'
},
output: {
// 打包后的文件名字
filename: '[name].bundle.js',
// 打包出的文件放在哪里(绝对路径)
path:path.resolve(_dirname, 'dist')
}
执行打包命令后会生成三个文件,共享的模块会被单独抽离出来
动态导入
webpack中只配置一个入口
const path = require('path');
module.exports = {
entry: {
index: './src/index.js'
},
output: {
filename: 'index.bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
index.js文件中使用动态导入
import('./common').then(() => {
console.log('动态导入成功');
});
运行webpack会生成两个bundle