用了哪些loader?
- vue-loader: 把vue转成js
- sass-loader: 把sass转成css
- css-loader: 把css转成js
- style-loader: 把css注入到js里,通过dom操作
- stylus-loader:加载并编译stylus文件
- file-loader: 把文件输出到文件夹中使用相对路径引用
- url-loader: 与file-loader类似,唯一不同的是可以添加阈值,超过limit阈值生成带有hash后缀的文件,不超过阈值的转化成base64
- image-loader:加载并压缩文件
- bable-loader:es6转成es5
- ts-loader:ts转成js
- eslint-loader:eslint校验
- html-loader:
更多可查看官方文档:https://webpack.docschina.org/loaders/
用了哪些plugin:
- html-webpack-plugin:生成html文件
- terser-webpack-plugin: 压缩js
- define-plugin: 允许编译的时候配置全局变量;比如版本号
- HotModuleReplacementPlugin:启用热更新
- mini-css-extract-plugin:css提取为独立文件
- ignore-plugin:忽略部分文件
- CommonsChunkPlugin 插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。
- UglifyJsPlugin使用
说下plugin和loader的区别
- loader是函数,转化为webpack支持的语言js,比如css->js注入dom中;在module.rules 中配置,从后向前编译
module.exports = { module: { rules: [ { test: /\.css$/, use: [ // [style-loader](/loaders/style-loader) { loader: 'style-loader' }, // [css-loader](/loaders/css-loader) { loader: 'css-loader', options: { modules: true } }, // [sass-loader](/loaders/sass-loader) { loader: 'sass-loader' } ] } ] } }- plugin是插件,可以拓展weboack的功能,使用webpack的钩子函数,监听webpack的生命周期事件,在合适的情况下添加逻辑,比如可以把css单独提取出来作为css文件;在plugins中单独配置,数组,每个里面new
const CompressionPlugin = require("compression-webpack-plugin"); module.exports = { plugins: [new CompressionPlugin()], }; And run webpack via your preferred method.为什么要用webpack
- 再没有webpack之前,使用js需要全部写在html的script标签里面,涉及到先后加载顺序
- 全局变量的污染,后来使用了自执行函数,闭包
- 改动一处,所有使用到的都会编译,不会按需加载
webpack的工作流程是什么?
- 初始化:读取配置,加载plugin,实例化compiler
- 编译:从entry出发,针对Module串行调用loader翻译文件的内容,如果该Module还有依赖的Module,递归的编译处理
- 输出:将编译之后的Module组成chunk,将chunk转换成文件,输出到文件系统里。
模块打包原理?
webpack为每个文件提供一个导入导出环境,不影响原来的代码逻辑跟顺序
文件监听原理?
当代码发生改变的时候,自动构建新的输出文件;
轮训判断是否改变,如果某个文件发生改变,不会立刻告诉监听器,先会缓存起来,等到aggregateTimeout时间后再改变。
确定是:需要刷新浏览器手动刷新
两种方式:- 执行命令的时候 加上–watch(vue-cli使用的是这种)
- 配置里添加上 watch:true
module.export = { // 默认false,也就是不开启 watch: true, // 只有开启监听模式时,watchOptions才有意义 watchOptions: { // 默认为空,不监听的文件或者文件夹,支持正则匹配 ignored: /node_modules/, // 监听到变化发生后会等300ms再去执行,默认300ms aggregateTimeout:300, // 判断文件是否发生变化是通过不停询问系统指定文件有没有变化实现的,默认每秒问1000次 poll:1000 }}说下webpack的热更新原理?
Webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。
vue文件 保存时 浏览器不刷新,页面改动的话,拿到带有hash的文件:
具体流程如下:
以下截图为send页面,改动页面时请求的数据,当本地资源发生变化时,实际上 webpack-dev-server 与浏览器之间维护了一个 Websocket,webpack-dev-server 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比,改变的话就把当前请求生成一个hash,客户端比对发现差异之后,会向webpack-dev-server发起ajax再次请求带有hash后缀的文件,下次再改变的时候生成的send文件加上该hash后缀。
当 HMR 失败后,回退到 live reload 操作,也就是进行浏览器刷新来获取最新打包代码。这又是我们项目里最常见的之前的业务弹层都没了 直接刷新当前页面。
- 文件指纹是什么?怎么用?
文件指纹就是文件的后缀
- hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改,常见于图片,文件后缀的hash构建
vue-cli是asset文件下都用hash;图片,svg,media,fonts;路径packages/@vue/cli-service/lib/config/assets.js

2. contenthash:根据文件内容来定义hash,contenthash就更新;常用于css, js
Vue-cli源码打包




- Chunkhash:根据webpack打包的chunk有关,不同的entry会生成不同的chunkhash,常用于output里的filename输出
module.exports = {
entry: {
app: './scr/app.js',
search: './src/search.js'
},
output: {
filename: '[name][chunkhash:8].js',
path:__dirname + '/dist'
},
plugins:[
new MiniCssExtractPlugin({
filename: `[name][contenthash:8].css`
})
]}
- 如何优化 Webpack 的构建速度
查看官网
- 使用高版本的webpack和nodejs
- 较新的版本能够建立更高效的模块树以及提高解析速度
- 多进程构建:thread-loader
if (useThreads) {
addLoader({
name: 'thread-loader',
loader: require.resolve('thread-loader'),
options:
typeof projectOptions.parallel === 'number'
? { workers: projectOptions.parallel }
: {}
})
}
- include,exclude 缩小loader范围
- noParse 对完全不需要解析的库进行忽略
webpackConfig.module.noParse(/^(vue|vue-router|vuex|vuex-router-sync)$/) - 合理使用alias 一般都只对src 最外层设置别名
webpackConfig.module..alias.set('@', api.resolve('src'))
- 使用html-webpack-externals-plugin时,将基础服务使用cdn引用,比如wx相关的cdn引入
- 提取公共代码时 使用SplitChunksPlugin替代commonChunkPlugin
packages/@vue/cli-service/lib/config/app.js
// vue-cli的源码 code splitting
if (process.env.NODE_ENV !== 'test') {
webpackConfig.optimization.splitChunks({
cacheGroups: {
defaultVendors: {
name: `chunk-vendors`, // 使用三方的
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: `chunk-common`, // 公共的
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
})
}

- treeShaking 标记是否有副作用,移除未引用的上下文,配合UglifyJsPlugin使用
- 将第三方库(library)(例如 lodash)提取到单独的 vendor chunk 文件中,因为他们不会频繁的改动,所以放在缓存里比较好
- 使用HashedModuleIdsPlugin防止vender里面因为module.id改变而导致hash改变。达到缓存的效果
- 使用 DllPlugin 将更改不频繁的代码进行单独编译
- 使用 cache-loader 启用持久化缓存。使用 package.json 中的 “postinstall” 清除缓存目录。
addLoader({
name: 'cache-loader',
loader: require.resolve('cache-loader'),
options: api.genCacheConfig('ts-loader', {
'ts-loader': require('ts-loader/package.json').version,
'typescript': require('typescript/package.json').version,
modern: !!process.env.VUE_CLI_MODERN_BUILD
}, 'tsconfig.json')
})
- terser-webpack-plugin/uglify-webpack-plugin 代码压缩
if (process.env.NODE_ENV === 'production') {
const TerserPluginV4 = require('terser-webpack-plugin')
config.optimization.minimizer('terser').init(
(Plugin, [terserPluginOptions]) =>
new TerserPluginV4({
sourceMap: rootOptions.productionSourceMap,
cache: true,
...terserPluginOptions
})
)
- mini-css-extract-plugin 提取css文件,通过 css-loader 的 minimize 选项开启 cssnano 压缩 CSS
// inject CSS extraction plugin 注入提取css插件
if (shouldExtract) {
webpackConfig
.plugin('extract-css')
.use(require('mini-css-extract-plugin'), [extractOptions])
// minify extracted CSS 压缩提取的css
webpackConfig.optimization
.minimizer('css')
.use(require('css-minimizer-webpack-plugin'), [{
parallel: rootOptions.parallel, // 多进程并行压缩
sourceMap: rootOptions.productionSourceMap && sourceMap,
minimizerOptions: cssnanoOptions
}])
}
- 图片资源处理。base64
const inlineLimit = 4096
const vueMajor = getVueMajor(api.getCwd())
const supportsEsModuleAsset = (vueMajor !== 2)
const genAssetSubPath = dir => {
return getAssetPath(
options,
`${dir}/[name]${options.filenameHashing ? '.[hash:8]' : ''}.[ext]`
)
}
const genUrlLoaderOptions = dir => {
return {
limit: inlineLimit,
esModule: supportsEsModuleAsset,
// use explicit fallback to avoid regression in url-loader>=1.1.0
fallback: {
loader: require.resolve('file-loader'),
options: {
name: genAssetSubPath(dir),
esModule: supportsEsModuleAsset
}
}
}
}
api.chainWebpack(webpackConfig => {
webpackConfig.module
.rule('images')
.test(/\.(png|jpe?g|gif|webp)(\?.*)?$/)
.use('url-loader')
.loader(require.resolve('url-loader'))
.options(genUrlLoaderOptions('img'))
// do not base64-inline SVGs.
// https://github.com/facebookincubator/create-react-app/pull/1180
webpackConfig.module
.rule('svg')
.test(/\.(svg)(\?.*)?$/)
.use('file-loader')
.loader(require.resolve('file-loader'))
.options({
name: genAssetSubPath('img'),
esModule: supportsEsModuleAsset
})
webpackConfig.module
.rule('media')
.test(/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/)
.use('url-loader')
.loader(require.resolve('url-loader'))
.options(genUrlLoaderOptions('media'))
webpackConfig.module
.rule('fonts')
.test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/i)
.use('url-loader')
.loader(require.resolve('url-loader'))
.options(genUrlLoaderOptions('fonts'))
})
代码分隔的本质是什么 (splitChunksPlugin,commonChunkPlugin)
在源代码和打包一个文件之间找个平衡,在服务器能承受的压力下达到更好的用户体验。其实就是按需加载 ,三方的依赖js打包成vendor.hash[8].js 搞成缓存 使用HashedModulePlus记住id不改变,公共的组件打包成common.hash[8].js
其他的尽量按需加载,按照模块放在一个chunkName里面
- 如果直接源代码的话,请求太多,图片&js都算
- 如果直接打包成一个js文件 代码量过多,白屏时间更长
有自己写过plugin吗?
没有 但是可以参考vue-cli里的packages/@vue/cli-service/lib/webpack/ModernModePlugin.js
具体可看官网Plugin API
自己写过loader吗?
具体可看官网loader API