从vue-cli4源码分析,看看做了哪些事情,还可以怎样优化

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.代码分割细化

如果对您有帮助,请点个赞,谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值