vue-cli4 从源码分析看vue-cli4为我们做了什么事情
1.webpack的全局模块配置:
1.1 输出文件的配置
1.2 非测试环境下,webpack版本为4时,配置webpackConfig.optimization.splitChunks进行代码分割优化,把node_modules的初始化依赖文件提取到chunk-vendors.js文件中
1.3 优化代码块的块ID
1.4 js,css在index.html页面引入是,link配置rel=‘preload’ 和给html配置crossorigin属性
1.5 用copy-webpack-plugin插件复制public文件下的文件到outputDir的文件下
2 对静态资源的处理,包括image,svg,media,font
2.1 静态资源的构建输出默认用[hash:8]
2.2 用url-loader插件对图片的处理
2.3 用url-loader插件对媒体文件的处理
2.4 用url-loader插件对字体的处理
3 对样式的处理
3.1 css的构建输出默认用[contenthash:8]
3.2 支持5中格式的文件对postcss进行配置
3.3 对各种css语言的规则配置,包括css,postcss,scss,sass,less,stylus
3.4 用mini-css-extract-plugin对css进行提取,用css-minimizer-webpack-plugin对css进行压缩
4 对production模式进行优化处理
4.1 生产环境下,关闭资源地图,提高构建速度和避免资源被定位到原始资源
4.2 哈希模块ID生效,替代原来的自增ID,让没有改动的模块或者新增的模块不会影响其他模块ID,从而达到缓存的作用
4.3 非生产环境下,关闭压缩的优化,提高构建的速度
5 关于entry,output,module,resolve,vue-loader的处理,terser-webpack-plugin插件对js的压缩处理
5.1 development环境的默认配置
5.2 resolve解释模块的配置
5.3 不解析的模块
5.4 配置.vue文件的加载器。一些性能开销较大的 loader 前面添加 cache-loader,将结果缓存在磁盘中减少编译时间
5.5 不使用polyfill模块
5.6 环境变量的配置
5.7 用terser-webpack-plugin插件对js进行压缩操作
5.8 terser-webpack-plugin插件的配置参数
1.webpack的全局模块配置:
1.1 输出文件的配置
// 文件输出的哈希值是[contenthash:8]
const outputFilename = getAssetPath(
options,
`js/[name]${isLegacyBundle ? `-legacy` : ``}${isProd && options.filenameHashing ? '.[contenthash:8]' : ''}.js`
)
webpackConfig
.output
.filename(outputFilename)
.chunkFilename(outputFilename)
1.2 非测试环境下,webpack版本为4时,配置webpackConfig.optimization.splitChunks进行代码分割优化,把node_modules的初始化依赖文件提取到chunk-vendors.js文件中。
if (process.env.NODE_ENV !== 'test') {
if (webpackMajor === 4) {
webpackConfig.optimization.splitChunks({
cacheGroups: {
vendors: {
name: `chunk-vendors`,
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: `chunk-common`,
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
})
} else {
//非测试环境下,webpack版本不为4时,进行代码分割优化
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
}
}
})
}
}
1.3 优化代码块的块ID
if (webpackMajor === 4 && isProd) {
// In webpack 5, optimization.chunkIds is set to `deterministic` by default in production
// In webpack 4, we use the following trick to keep chunk ids stable so async chunks have consistent hash (#1916)
webpackConfig
.plugin('named-chunks')
.use(webpack.NamedChunksPlugin, [chunk => {
if (chunk.name) {
return chunk.name
}
const hash = require('hash-sum')
const joinedHash = hash(
Array.from(chunk.modulesIterable, m => m.id).join('_')
)
return `chunk-` + joinedHash
}])
}
1.4 js,css在index.html页面引入是,link配置rel=‘preload’ 和给html配置crossorigin属性
if (options.crossorigin != null || options.integrity) {
webpackConfig
.plugin('cors')
.use(require('../webpack/CorsPlugin'), [{
crossorigin: options.crossorigin,
integrity: options.integrity,
publicPath: options.publicPath
}])
}
1.5 用copy-webpack-plugin插件复制public文件下的文件到outputDir的文件下
const publicDir = api.resolve('public')
if (!isLegacyBundle && fs.existsSync(publicDir)) {
webpackConfig
.plugin('copy')
.use(require('copy-webpack-plugin'), [{
patterns: [{
from: publicDir,
to: outputDir,
toType: 'dir',
globOptions: {
ignore: publicCopyIgnore
}
}]
}])
}
2 对静态资源的处理,包括image,svg,media,font
2.1 静态资源的构建输出默认用[hash:8]
const genAssetSubPath = dir => {
return getAssetPath(
options,
`${dir}/[name]${options.filenameHashing ? '.[hash:8]' : ''}.[ext]`
)
}
const genUrlLoaderOptions = dir => {
return {
limit: inlineLimit, //inlineLimit==4096B,默认小于4096的图片进行base64压缩到js里面,不做抽离
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
}
}
}
}
2.2 用url-loader插件对图片的处理
webpackConfig.module
.rule('images')
.test(/\.(png|jpe?g|gif|webp)(\?.*)?$/)
.use('url-loader')
.loader(require.resolve('url-loader'))
.options(genUrlLoaderOptions('img'))
2.2 用file-loader插件对svg格式图片的处理
webpackConfig.module
.rule('svg')
.test(/\.(svg)(\?.*)?$/)
.use('file-loader')
.loader(require.resolve('file-loader'))
.options({
name: genAssetSubPath('img'),
esModule: supportsEsModuleAsset
})
2.3 用url-loader插件对媒体文件的处理
webpackConfig.module
.rule('media')
.test(/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/)
.use('url-loader')
.loader(require.resolve('url-loader'))
.options(genUrlLoaderOptions('media'))
2.4 用url-loader插件对字体的处理
webpackConfig.module
.rule('fonts')
.test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/i)
.use('url-loader')
.loader(require.resolve('url-loader'))
.options(genUrlLoaderOptions('fonts'))
3 对样式的处理
3.1 css的构建输出默认用[contenthash:8]。
const filename = getAssetPath(
rootOptions,
`css/[name]${rootOptions.filenameHashing ? '.[contenthash:8]' : ''}.css`
)
const extractOptions = Object.assign({
filename,
chunkFilename: filename
}, extract && typeof extract === 'object' ? extract : {})
3.2 支持5中格式的文件对postcss进行配置
const hasPostCSSConfig = !!(loaderOptions.postcss || api.service.pkg.postcss || findExisting(api.resolve('.'), [
'.postcssrc',
'.postcssrc.js',
'postcss.config.js',
'.postcssrc.yaml',
'.postcssrc.json'
]))
3.3 对各种css语言的规则配置,包括css,postcss,scss,sass,less,stylus
function createCSSRule (lang, test, loader, options) {
const baseRule = webpackConfig.module.rule(lang).test(test)
// rules for <style lang="module">
const vueModulesRule = baseRule.oneOf('vue-modules').resourceQuery(/module/)
applyLoaders(vueModulesRule, true)
// rules for <style>
const vueNormalRule = baseRule.oneOf('vue').resourceQuery(/\?vue/)
applyLoaders(vueNormalRule, false)
// rules for *.module.* files
const extModulesRule = baseRule.oneOf('normal-modules').test(/\.module\.\w+$/)
applyLoaders(extModulesRule, true)
// rules for normal CSS imports
const normalRule = baseRule.oneOf('normal')
applyLoaders(normalRule, !requireModuleExtension)
function applyLoaders (rule, isCssModule) {
if (shouldExtract) {
rule
.use('extract-css-loader')
.loader(require('mini-css-extract-plugin').loader)
.options({
publicPath: cssPublicPath,
// TODO: enable this option later
esModule: false
})
} else {
rule
.use('vue-style-loader')
.loader(require.resolve('vue-style-loader'))
.options({
sourceMap,
shadowMode
})
}
const cssLoaderOptions = Object.assign({
sourceMap,
importLoaders: (
1 + // stylePostLoader injected by vue-loader
1 + // postcss-loader
(needInlineMinification ? 1 : 0)
)
}, loaderOptions.css)
if (isCssModule) {
cssLoaderOptions.modules = {
localIdentName: '[name]_[local]_[hash:base64:5]',
...cssLoaderOptions.modules
}
} else {
delete cssLoaderOptions.modules
}
rule
.use('css-loader')
.loader(require.resolve('css-loader'))
.options(cssLoaderOptions)
if (needInlineMinification) {
rule
.use('cssnano')
.loader(require.resolve('postcss-loader'))
.options({
sourceMap,
postcssOptions: {
plugins: [require('cssnano')(cssnanoOptions)]
}
})
}
rule
.use('postcss-loader')
.loader(require.resolve('postcss-loader'))
.options(Object.assign({ sourceMap }, loaderOptions.postcss))
if (loader) {
let resolvedLoader
try {
resolvedLoader = require.resolve(loader)
} catch (error) {
resolvedLoader = loader
}
rule
.use(loader)
.loader(resolvedLoader)
.options(Object.assign({ sourceMap }, options))
}
}
}
3.4 用mini-css-extract-plugin对css进行提取,用css-minimizer-webpack-plugin对css进行压缩
if (shouldExtract) {
//css的提取操作
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
}])
}
4 对production模式进行优化处理,
4.1 生产环境下,关闭资源地图,提高构建速度和避免资源被定位到原始资源
webpackConfig
.mode('production')
.devtool(options.productionSourceMap ? 'source-map' : false)
const { semver } = require('@vue/cli-shared-utils')
const webpack = require('webpack')
//wepack的版本
const webpackMajor = semver.major(webpack.version)
// DeterministicModuleIdsPlugin is only available in webpack 5
// (and enabled by default in production mode).
// In webpack 4, we need HashedModuleIdsPlugin
// to keep module.id stable when vendor modules does not change.
// It is "the second best solution for long term caching".
// <https://github.com/webpack/webpack/pull/7399#discussion_r193970769>
4.2 哈希模块ID生效,替代原来的自增ID,让没有改动的模块或者新增的模块不会影响其他模块ID,从而达到缓存的作用
if (webpackMajor === 4) {
webpackConfig.optimization.set('hashedModuleIds', true)
}
// disable optimization during tests to speed things up
4.3 非生产环境下,关闭压缩的优化,提高构建的速度
if (process.env.VUE_CLI_TEST && !process.env.VUE_CLI_TEST_MINIMIZE) {
webpackConfig.optimization.minimize(false)
}
}
5 关于entry,output,module,resolve,vue-loader的处理,terser-webpack-plugin插件对js的压缩处理
5.1 development环境的默认配置
webpackConfig
.mode('development')
.context(api.service.context)
.entry('app')
.add('./src/main.js')
.end()
.output
.path(api.resolve(options.outputDir))
.filename(isLegacyBundle ? '[name]-legacy.js' : '[name].js')
.publicPath(options.publicPath)
5.2 resolve解释模块的配置
webpackConfig.resolve
.extensions
.merge(['.mjs', '.js', '.jsx', '.vue', '.json', '.wasm'])
.end()
.modules
.add('node_modules')
.add(api.resolve('node_modules'))
.add(resolveLocal('node_modules'))
.end()
.alias
.set('@', api.resolve('src'))
5.3 不解析的模块
webpackConfig.module.noParse(/^(vue|vue-router|vuex|vuex-router-sync)$/)
5.4 配置.vue文件的加载器。一些性能开销较大的 loader 前面添加 cache-loader,将结果缓存在磁盘中减少编译时间
//https://www.cnblogs.com/zhonglinfeng666/p/13388809.html
webpackConfig.module
.rule('vue')
.test(/\.vue$/)
.use('cache-loader')
.loader(require.resolve('cache-loader'))
.options(vueLoaderCacheConfig)
.end()
.use('vue-loader')
.loader(require.resolve('vue-loader-v15'))
.options(Object.assign({
compilerOptions: {
whitespace: 'condense'
}
}, vueLoaderCacheConfig))
webpackConfig
.plugin('vue-loader')
.use(require('vue-loader-v15').VueLoaderPlugin)
5.5 不使用polyfill模块
webpackConfig.node
.merge({
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// process is injected via DefinePlugin, although some 3rd party
// libraries may require a mock to work properly (#934)
process: 'mock',
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
})
5.6 环境变量的配置
const resolveClientEnv = require('../util/resolveClientEnv')
webpackConfig
.plugin('define')
.use(webpack.DefinePlugin, [
resolveClientEnv(options)
])
5.7 用terser-webpack-plugin插件对js进行压缩操作
const TerserPlugin = require('terser-webpack-plugin')
const terserOptions = require('./terserOptions')
webpackConfig.optimization
.minimizer('terser')
.use(TerserPlugin, [terserOptions(options)])
5.8 terser-webpack-plugin插件的配置参数
module.exports = options => ({
terserOptions: {
compress: {
// turn off flags with small gains to speed up minification
arrows: false,
collapse_vars: false, // 0.3kb
comparisons: false,
computed_props: false,
hoist_funs: false,
hoist_props: false,
hoist_vars: false,
inline: false,
loops: false,
negate_iife: false,
properties: false,
reduce_funcs: false,
reduce_vars: false,
switches: false,
toplevel: false,
typeofs: false,
// a few flags with noticeable gains/speed ratio
// numbers based on out of the box vendor bundle
booleans: true, // 0.7kb
if_return: true, // 0.4kb
sequences: true, // 0.7kb
unused: true, // 2.3kb
// required features to drop conditional branches
conditionals: true,
dead_code: true,
evaluate: true
},
mangle: {
safari10: true
}
},
sourceMap: options.productionSourceMap,
cache: true,
parallel: options.parallel,
extractComments: false
})
所以,我们还可以做的优化有,如下:
1.webpack-cdn打包优化
2.image-webpack-plugin图片压缩优化
3.webpack-gzip构建压缩优化
4.代码分割细化
如果对您有帮助,请点个赞,谢谢。