Appearance
webpack
vite 和 webpack 的区别
- 开发模式下的构建速度
- vite 利用原生 ES 模块加载能力,可以直接启动项目,然后再按需编译(访问哪个页面再编译对应页面),这就导致比 webpack 启动速度快很多。
- webpack 则是会打包整个项目,然后在启动。
- 热更新处理
- Vite 中,当某个模块内容改变时,只需要让浏览器重新请求该模块,这大大减少了热更新的时间。
- 在 Webpack 中,当一个模块或其依赖的模块内容改变时,需要重新编译这些模块。
- 生产构建的区别
- Vite 使用 Rollup 进行打包,不会将所有代码打包成一个 bundle,它保留了第三方依赖的单独引用,以实现更快的加载速度。
- Webpack 在生产环境下,它将所有代码打包成一个或多个 bundle。
- 兼容性
- vite 兼容性较差,依赖于 ES 模块和浏览器原生支持。
- webpack 兼容性较好,支持更多浏览器,和一些旧版本浏览器,通过 polyfills 和其他手段来确保兼容性。
- 配置及插件生态
- vite 基本开箱就用,需要配置的东西很少,它目前的生态没有 webpack 好。
- webpack 配置较为复杂,对新手不太友好,它的生态比较丰富。
说说 webpack proxy 工作原理?为什么能解决跨域?
是利用 webpack-dev-server
在本地开启了一个服务器,利用服务器之间没有跨域的行为,通过访问本地服务器,本地服务器再去访问接口,从而实现的解决跨域问题。
webpack 分割代码
一个单页应用,如果不进行设置,大部分文件都会被打包到同一个 js 文件中,这就会导致文件过于庞大,加载慢。
利用 optimization.splitChunks
则可以根据具体规则进行拆分,提高首屏加载速度。
js
const path = require('path')
module.exports = {
optimization: {
splitChunks: {
chunks: 'all', // all 对同步和异步代码都进行分割
cacheGroups: {
// libs: { // 最好是不要把 node_modules 里面的包都放在一起,会很大
// name: 'libs', // 文件导出名称
// test(module) {
// // 返回一个布尔值
// // `module.resource` 包含磁盘上文件的绝对路径。
// // 请注意使用 `path.sep` 而不是 / 或 \,以实现跨平台兼容性。
// return (
// module.resource &&
// module.resource.includes(`${path.sep}node_modules`)
// )
// },
// priority: -10, // 该规则的优先级,数字越大,当符合多个规则的时候,优先级越高,尽量用负数,因为默认是0
// minSize: 1, // 当文件大于此大小的时候,进行分割 ,实际开发中一般为 5 * 1024 ,即 5KB
// minChunks: 1, // 最少被引用的次数,满足此条件就会进行分割
// },
echarts: {
name: 'echarts-lib',
test(module) {
return (
module.resource &&
module.resource.includes(
`${path.sep}node_modules${path.sep}echarts`
)
)
},
priority: -8,
minSize: 1,
minChunks: 1,
},
},
},
},
}
vite 分割代码
js
// 方法1 推荐
build: {
// rollup 配置
rollupOptions: {
output: {
// key自定义 value[] 插件同步package.json名称 或 src/相对路径下的指定文件 (自己可以看manualChunks ts类型)
manualChunks: {
// vue vue-router合并打包
vue: ['vue', 'vue-router'],
echarts: ['echarts'],
lodash: ['lodash'],
// 两个文件合并成一个helloWorld文件
helloWorld: ['src/components/HelloWorld.vue','src/components/HelloWorld1.vue'],
}
}
}
}
// 方法二
build: {
// rollup 配置
rollupOptions: {
output: {
manualChunks(id: any): string {
if (id.includes("style.css")) {
// 需要单独分割那些资源 就写判断逻辑就行
return 'src/style.css';
}
if (id.includes("HelloWorld.vue")) {
// 单独分割hello world.vue文件
return 'src/components/HelloWorld.vue';
}
if(id.includes('node_modules/echarts')){
return 'echarts'
}
// 最小化拆分包
if (id.includes("node_modules")) {
return id.toString().split("node_modules/")[1].split("/")[0].toString();
}
}
}
}
}
Loader 和 Plugin 的不同
- loader:意思为"加载器"。它的作用是让 webpack 拥有加载和解析非 js 文件的功能。因为 webpack 将一切文件都视为模块,且它本身只提供了解析 js 文件的能力。
webpack 要求 loader 导出一个函数,函数有个参数,为接收的资源文件,并且函数最终返回一个 js 字符串。
js
// markerdown-loader.js 实现一个把md文件转成html的loader
const marked = require('marked')
module.exports = (source) => {
// source 就是文件内容
const html = marked(source)
return `export default ${JSON.stringify(html)}`
}
js
//webpack.config.js
const path = require('path')
module.exports = {
entry: './src/main.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'index.js',
},
module: {
rules: [
{
test: /.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
{
test: /.md$/,
use: {
loader: './markerdown-loader.js',
},
},
],
},
}
- plugin:意思为"插件"。它的作用是扩展 webpack 的功能。 webpack 在运行的时候,生命周期会广播出很多事件, plugin 则可以去监听这些事件,改变 webpack 的输出结果。
webpack 要求插件是一个函数或者一个包含 apply 方法的对象。webpack 在启动过后会广播出很多钩子,我们可以通过钩子,添加自己的方法进行处理,下面列举一些常见的钩子函数:
完整的钩子官网:https://www.webpackjs.com/api/compiler-hooks/
生命周期钩子函数,是由 compiler 暴露,可以通过如下方式访问 compiler.hooks.<hooks-name>.tap(...)
js
// sky-plugin.js 清除打包后js文件中的 /******/ 注释
class SkyPlugin {
// apply 方法在 webpack 启动时会被调用
apply(compiler) {
// compiler 包含了此次构建的所有配置信息
// 监听 emit hooks(生成资源到 output 目录之前),对文件进行处理
compiler.hooks.emit.tap('SkyPlugin', (compilation) => {
// compilation 可以理解为此次打包的上下文
// compilation.assets 打包后生成的所有资源文件
for (const name in compilation.assets) {
// name 文件名
// compilation.assets[name].source() 文件内容
if (name.endsWith('.js')) {
// 获取文件内容
const contents = compilation.assets[name].source() // 对文件内容进行处理,清除文件中的 /******/ 注释
const afterContents = contents.replace(/\/\*\*+\*\//g, '') // 把处理后的文件内容,覆盖返回的结果
compilation.assets[name] = {
source: () => afterContents,
size: () => afterContents.length,
}
}
}
})
}
}
module.exports = SkyPlugin
常用 loader
名称 | 作用 |
---|---|
style-loader | 将 css 添加到 DOM 的内联样式标签 style 里 |
css-loader | 允许将 css 文件通过 require 的方式引入,并返回 css 代码 |
less-loader | 处理 less |
postcss-loader | 用 postcss 来处理 CSS |
autoprefixer-loader | 处理 CSS3 属性前缀,已被弃用,建议直接使用 postcss |
file-loader | 分发文件到 output 目录并返回相对路径 |
url-loader | 和 file-loader 类似,但是当文件小于设定的 limit 时可以返回一个 Data Url |
html-minify-loader | 压缩 HTML |
babel-loader | 用 babel 来转换 ES6 代码 |
常用 plugin
名称 | 作用 |
---|---|
html-webpack-plugin | 在打包结束后,⾃动生成⼀个 html ⽂文件,并把打包生成的 js 模块引⼊到该 html 中 |
clean-webpack-plugin | 删除(清理)构建目录 |
mini-css-extract-plugin | 提取 CSS 到一个单独的文件中 |
DefinePlugin | 允许在编译时创建配置的全局对象,是一个 webpack 内置的插件,不需要安装 |
copy-webpack-plugin | 复制文件或目录到执行区域,如 vue 的打包过程中,如果我们将一些文件放到 public 的目录下,那么这个目录会被复制到 dist 文件夹中 |
js
// webpack.config.js
const SkyPlugin = require('./sky-plugin')
const path = require('path')
module.exports = {
entry: './src/main.js',
mode: 'development',
output: {
path: path.join(__dirname, 'dist'),
filename: 'app.js',
},
module: {
rules: [
{
//文件后缀
test: /\.js$/,
use: 'babel-loader', //不需要编译的文件
exclude: /(node_modules)/,
},
],
},
plugins: [new SkyPlugin()],
}
各种 source-map 的区别
开发模式推荐配置:eval-cheap-module-source-map
- 本地开发首次打包慢点没关系,因为 eval 缓存的原因,rebuild 会很快
- 开发中,我们每行代码不会写的太长,只需要定位到行就行,所以加上 cheap
- 我们希望能够找到源代码的错误,而不是打包后的,所以需要加上 module
生产模式不设置,不暴露源码。
webpack 文件的三种 hash
例如我们在基础配置中用到的:filename: "[name][hash:8][ext]"
。
这里里面 [] 包起来的,就叫占位符,它们都是什么意思呢?请看下面这个表
占位符 | 含义 |
---|---|
ext | 文件后缀名 |
name | 文件名 |
path | 文件相对路径 |
folder | 文件所在文件夹 |
hash | 每次构建生成的唯一 hash 值 |
chunkhash | 根据 chunk 生成 hash 值 |
contenthash | 根据文件内容生成 hash 值 |
- hash :任何一个文件改动,整个项目的构建 hash 值都会改变。
- chunkhash:文件的改动只会影响其所在 chunk 的 hash 值,chunk 是指该文件所有的引用关系。
- contenthash:每个文件都有单独的 hash 值,文件的改动只会影响自身的 hash 值。 js、css 文件建议使用这个类型
webpack 优化构建速度
- 使用 resolve.alias 给一些常用的路径起一个别名,来减少 webpack 查找过程,不然通过
../../
形式会查找的慢一点
js
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
}
- 配置 thread-loader 开启多线程。小型项目不需要,不然有反效果。而且不需要所有 loader 都开启多线程,仅在耗时的 loader 中开启。
js
// vue.config.js
module.exports = {
configureWebpack: {
// ...省略
module: {
rules: [
{
test: /\.js$/,
include: path.join(__dirname, 'src'),
exclude: /node_modules/,
use: [
{
loader: 'thread-loader',
options: {
// 指定 worker 线程的数量
workers: 2,
},
},
'babel-loader',
],
},
],
},
},
}
- 开启缓存。vue-cli 已经内置了 cache-loader 和 babel-loader 的缓存方式,不需要再配置这两种,这里我们使用 webpack 5 的
hard-source-webpack-plugin
。
js
// 介绍 babel-loader 开启缓存
const config = {
module: {
rules: [
{
test: /\.js$/i,
include: path.join(__dirname, 'src'),
exclude: /node_modules/,
use: [
// ...
{
loader: 'babel-loader',
options: {
cacheDirectory: true, // 启用缓存
},
},
],
},
],
},
}
// 介绍 cache-loader 开启缓存,需要先安装 cache-loader
const config = {
module: {
rules: [
{
test: /\.(s[ac]|c)ss$/i,
use: [
// 'style-loader',
MiniCssExtractPlugin.loader,
'cache-loader', // 获取前面 loader 转换的结果
'css-loader',
'postcss-loader',
'sass-loader',
],
},
],
},
}
js
// 使用 hard-source-webpack-plugin 首次打包会更慢,后续才会更快
module.exports = {
configureWebpack: {
// ...省略
plugins: [
new HardSourceWebpackPlugin({
root: process.cwd(),
directories: [],
environmentHash: {
root: process.cwd(),
directories: [],
files: ['package.json', 'yarn.lock'],
},
}),
],
},
}
说说 webpack 的构建流程
- 初始化流程:从配置文件和 Shell 语句中读取与合并参数,并初始化需要使用的插件和配置插件等执行环境所需要的参数
- 编译构建流程:从 Entry 发出,针对每个 Module 串行调用对应的 Loader 去翻译文件内容,再找到该 Module 依赖的 Module,递归地进行编译处理
- 输出流程:对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到文件系统
webpack 的热更新原理
- 通过 webpack-dev-server 创建两个服务器:提供静态资源的服务(express)和 Socket 服务
- express server 负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析)
- socket server 是一个 websocket 的长连接,双方可以通信
- 当 socket server 监听到对应的模块发生变化时,会生成两个文件.json(manifest 文件)和.js 文件(update chunk)
- 通过长连接,socket server 可以直接将这两个文件主动发送给客户端(浏览器)
- 浏览器拿到两个新的文件后,通过 HMR runtime 机制,加载这两个文件,并且针对修改的模块进行更新