Main Contents of this paper

  1. For old projects, I did the steps when upgrading webpack.
  2. Based on the product positioning and business development trend of the project, which aspects can I start from and think about during the reconstruction?

Background and solution

Because the company’s products are to put the same type of business software on different e-commerce platforms, so the new project is to transplant the old code and put it together after deletion and modification. Therefore, scattered file directory, introduction and export chaos, code redundancy, style is not uniform standard. These bugs result in code that is poorly readable and maintainable, with many style conflicts and strange, hard-to-reproduce bugs. I planned to transform and optimize the project in the iterative pit filling. My solution is as follows:

  • Step 1: Understand the basic business (about three months), ask customers for suggestions when on duty, and understand users’ usage habits and style positioning of our software. A detailed investigation was conducted on the competing products of the same type (about eight, which can be done by our software) to analyze the shortcomings and advantages of our products. At the same time, actively communicate with the product development direction and plan. In my opinion, when I took over the project, only 20% of the planned functions were completed, and there was still a high room for improvement. This process took about half a year.
  • Step 2: Based on the confusion of the project directory, I first redivided the project directory according to the functional modules, and put the router route path corresponding to the file path to facilitate the module search in the later stage.
  • The third step: unified entry of basic resources to public resources, which is conducive to the management and maintenance of resources in the later period, and there is no need to repeat the introduction of operations in the code. For example, public variables of the CSS can be imported globally through the Loader instead of manually. Font is dynamically loaded and locally stored SVG files are deleted.
  • Step 4: Because the Webpack version of the project is 2, and it is all handwritten, considering the later maintenance of colleagues and the planned step by step iteration and upgrade of myself, I did not use VUE-CLI, but also all hand matching. The detailed upgrade steps are also described below
  • Step 5: high reuse logic encapsulation and internal code logic optimization within the project. Since our project was faced with B-end clients, there were a lot of data queries, so I wrote a model with tabular queries, pagination and search functions, which was very convenient to cooperate with VUEX.

Iteration, optimization, writing, are more random, limited ability, so it takes a year or so, I hope you can give a thumb-up 👍💗.

Project Webpack upgrade

The configuration steps

First, comment out all the code of the entry file main.js, create folder :webpack in the root directory, create three files: webPack.common. js, webpack.development.js, webpack.product.js.

  1. Introduce a simple.vue file in main.js, just the template, and configure vue-loader to make the project run properly. Write the command line at script:
{
    "dev": "webpack-dev-server ./webpack/webpack.common.js --mode='development'"."build": "webpack --config ./webpack/webpack.common.js --mode='production'"
  }
Copy the code
  1. Write js code in the.vue file and configure Babel to make the project run properly.
  2. Write CSS and LESS code in the. Vue file and configure CSS and LESS to make the project run normally.
  3. Introduce images, fonts, etc. into the.vue file and configure static resources to make the project run properly.
  4. Just introduce simple components in the app.vue file and try to introduce a page to get the project running. The basic configuration of the project is complete
  5. Distinguish environment variables, package and configure script commands separately, and optimize the package script

Resource pack

Vue configuration

Vue-loader: Allows you to write VUE components in a format called single-file components (SFCs)

npm i -D vue-loader
Copy the code

Modules configuration

const { VueLoaderPlugin } = require('vue-loader')

{
    output: {
        path: path.resolve(__dirname, '.. /dist'),
        filename: '[name].[chunkhash].js'.chunkFilename: '[name].[chunkhash].js'.publicPath: '/'
    },
    plugins: [
        new VueLoaderPlugin(),
    ],
    module: {
      rules: [{test: /\.vue$/,
                use: [
                    {
                    loader: 'cache-loader'
                    },
                    {
                    loader: 'vue-loader'.options: {
                        transformAssetUrls: {
                        video: ['src'.'poster'].source: 'src'.img: 'src'.image: ['xlink:href'.'href'].use: ['xlink:href'.'href']},cssSourceMap: true.hotReload: true.compilerOptions: {
                        preserveWhitespace: true}}}].exclude: /node_modules/]}}},Copy the code

validation: Enters a command line on the terminalnpm run buildNo error was reported. Dist is shown in the figure below

Babel configuration

@babel/cli: is a command line tool provided by Babel, which is used to compile source code under command line. CacheDirectory :true, the cache file can be seen in node_modules/. Cache @babel/preset-env: ES2015+ code can be automatically converted to ES5 based on the target browser or runtime environment configured. UseBuiltIns :true can be imported on demand. Configure Corejs :3 Specify the corejs version. Core-js: It’s a polyfill of the JavaScript standard library, as modular as possible, allowing you to choose the functionality you want.

See article # Babel Compatibility implementation

npm install --save-dev @babel/core @babel/cli @babel/preset-env babel-loader @babel/plugin-transform-runtime
Copy the code

. Babelrc configuration

{
    "presets": [["@babel/preset-env", {
            "useBuiltIns": "usage"."corejs": 3."targets": {
              "browsers": ["1%" >."last 2 versions"."not ie <= 8"]}}]],"plugins": [
      "@babel/plugin-transform-runtime"]}Copy the code

The module configuration

{
    test: /\.js$/,
    use: [
      {
        loader: 'babel-loader'.options: {
          presets: ['@babel/preset-env'].babelrc: true.cacheDirectory: true // Enable caching}}].exclude: /node_modules/
}
Copy the code

"babel": "babel src/index.js --out-dir dist"Command to compile the SRC /index.js test file

npm run babelVerification of packaged results

css

Vue-style-loader: to analyze JS code into AST, convenient for each plug-in to analyze syntax for corresponding processing csS-loader: Parse @import and URL statements in CSS files, process CSS-modules, and return the result as a JS module to postCSs-loader: Autoprefixer: parses CSS files and adds browser prefixes to CSS content postCSS: less-loader that uses plug-ins to convert CSS: Translate less code into browser-aware CSS code style-resources-loader: Import some common style file variables for the CSS preprocessor

npm install --save-dev vue-style-loader css-loader postcss-loader autoprefixer postcss less-loader style-resources-loader
Copy the code

The module configuration

{
        test: /\.less$/,
        use: [
          'vue-style-loader',
          {
            loader: 'css-loader'.options: {
              importLoaders: 3}}, {loader: 'postcss-loader'.options: {
              indent: 'postcss'.plugins: (loader) = > [
                require('autoprefixer') ()// Add the prefix].sourceMap: false}}, {loader: 'less-loader'.options: {
              javascriptEnabled: true.sourceMap: true}}, {loader: 'style-resources-loader'.options: {
              patterns: [
                path.resolve(__dirname, '.. /src/assets/css/variables/*.less')],injector: (source, resources) = > {
                const combineAll = type= > resources
                  .filter(({ file }) = > file.includes(type))
                  .map(({ content }) = > content)
                  .join(' ')

                return combineAll('variables') + combineAll('mixins') + source
              }
            }
          }
        ],
        exclude: /node_modules/
      },
Copy the code

npm run buildVerification of packaged results

less

style-resources-loader: Avoid repeating @import in every style file. Use variables and common styles directly in each CSS file. Add style-resources-loader at the end of the CSS configuration, so you don’t need to manually import CSS variables

{
        loader: 'style-resources-loader'.options: {
          patterns: [
            path.resolve(__dirname, '.. /src/assets/css/variables/*.less')].injector: (source, resources) = > {
            const combineAll = type= > resources
              .filter(({ file }) = > file.includes(type))
              .map(({ content }) = > content)
              .join(' ')

            return combineAll('variables') + combineAll('mixins') + source
          }
}
Copy the code

npm run buildVerification of packaged results

MiniCssExtractPlugin: Extract CSS styles in JS, with link external introduction, reduce the size of JS files

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

{
    plugins: [
        new MiniCssExtractPlugin({
          filename: '[name].[contenthash].css',
          chunkFilename: '[id].[contenthash].css',
          ignoreOrder: true
        }),
    ]
}
Copy the code

The vue above it – style – loader replacement for MiniCssExtractPlugin. Loader

npm run buildVerification of packaged results

Image & SVG & Audio & FONT

Url-loader: resolves @import and URL statements in CSS files, processes CSS-modules, and returns the result as a JS module

npm install --save-dev svg-sprite-loader url-loader
Copy the code
 {
        test: /\.svg$/,
        loader: 'svg-sprite-loader'.include: [path.join(__dirname, '.. '.'src/assets/icon')].options: {
          symbolId: '[name]'.name: path.posix.join('static'.'img/[name].[hash:7].[ext]')}}, {test: /\.(png|jpe? g|gif)(\? . *)? $/,
        loader: 'url-loader'.exclude: /node_modules/,
        options: {
          limit: 10000.name: path.posix.join('static'.'img/[name].[hash:7].[ext]')}}, {test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\? . *)? $/,
        loader: 'url-loader'.exclude: /node_modules/,
        options: {
          limit: 10000.name: path.posix.join('static'.'media/[name].[hash:7].[ext]')}}, {test: /\.(woff|woff2? |eot|ttf|otf)(\? . *)? $/,
        loader: 'url-loader'.options: {
          limit: 10000.name: path.posix.join('static'.'fonts/[name].[hash:7].[ext]')}}Copy the code

Public sectorwebpack.common.jsTo optimize the

1. Externals excludes external dependencies and packages them into bundles

externals: {
  'vue': 'Vue',}Copy the code

npm run buildVerification of packaged resultsVue. Js package cannot be found in dist after externals is set. The following figure shows the screenshot before setting

2. Resolve Reduce the search scope and speed

resolve: { extensions: ['.js', '.vue', '.json'], alias: { 'vue$': 'vue/dist/vue.esm.js', '@': path.join(__dirname, '.. ', 'src'), '@services': path.join(__dirname, '.. ', 'src/api/services.js'), '@productsManagement': path.join(__dirname, '.. ', 'src/modules/productsManagement') } },Copy the code

3. The cache – loader cache

Cache-loader: Add the cache-loader before some loaders that require high performance to cache the results to the disk. This parameter is written before vue-loader

 {
    test: /\.vue$/,
    use: [
      {
        loader: 'cache-loader'
      },
      {
        loader: 'vue-loader'
      }
    ]
  },
Copy the code

npm run buildVerification of packaged results: You can see the cached files in node_modules. Cache

3. plugins

  1. DefinePlugin variable replacement
  2. WebpackBar Packaging progress display
  3. FriendlyErrorsWebpackPlugin configuration terminal output log
  4. HtmlWebpackPlugin dynamically generates HTML
  5. LodashModuleReplacementPlugin introduced as needed
  6. Thermal overload VueLoaderPlugin
  7. HardSourceWebpackPlugin caches internal webpack modules
  8. Thread-loader Multithreading packaging
const WebpackBar = require('webpackbar')
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin')
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')

const argv = require('yargs-parser')(process.argv.slice(-3))
const mode = argv.mode || 'development'
const isDev = mode === 'development'
const jsWorkerPool = {
  poolTimeout: 2000
}


plugins: [
    new webpack.DefinePlugin({
      'process.env': JSON.stringify(mode),
      'process.env.BUILD_ENV': JSON.stringify(mode)
    }),
    new WebpackBar({
      name: isDev ? 'development' : 'production'.color: isDev ? '#00953a' : '#f2a900'
    }),
    new FriendlyErrorsWebpackPlugin(),
    new LodashModuleReplacementPlugin(),
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      filename: 'index.html'.template: 'index.html'.inject: true.minify: {
        removeComments: true.collapseWhitespace: true.removeAttributeQuotes: true}}),new HardSourceWebpackPlugin({}),
],
Copy the code

HardSourceWebpackPlugin: provides intermediate cache for modules. The default cache path is node_modules/. Cache /hard-source

npm run buildAfter packaging,HardSourceWebpackPlugin results are verified

LodashModuleReplacementPlugin: This plugin will remove loDash features that you don’t neednpm run buildAfter packaging, LodashModuleReplacementPlugin results

Thread-loader: Place this loader before other loaders. Loaders placed after this loader will run in a separate worker pool to speed up packaging. We will not experiment here, because thread-loader is suitable for use on time-consuming loaders, otherwise it will slow down the speed.

4. optimization splitChunks& runtimeChunk(manifest)

15. SplitChunks: extracting files that have been repeatedly introduced and generating one or more files separately to avoid repackaging files in multiple entries script-ext-html-webpack-plugin: Inline runtimeChunk to our index.html runtimeChunk: The purpose is to extract the chunks mapping list separately from app.js, because the ID of each chunk is basically based on the hash of the content, so every change you make will affect it. If you do not extract it, app.js will change every time, and the cache will be invalid

const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin')

output: {
      path: path.resolve(__dirname, '.. /dist'),
      filename: '[name].[chunkhash].js'.chunkFilename: '[name].[chunkhash].js'.publicPath: '/'
},
plugins: [new ScriptExtHtmlWebpackPlugin({
      inline: /runtime\.. *\.js$/}),].optimization: {
    runtimeChunk: true.// Create a runtime to xx file
    splitChunks: {
      name: true.// Automatically process file names
      chunks: 'all'.automaticNameDelimiter: The '-'.cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: 10.name: 'vendors'.chunks: 'initial'
        },
        commons: {
          name: 'commons'.minChunks: 2.priority: 5.test: path.join(__dirname, '.. '.'src/components'),
          reuseExistingChunk: true}}}}Copy the code

Create it under SRC firstA. s, b.j sFile,main.jsthroughDynamic loading import()Introduction,webpackChunkNameA packaged name for import on demand.npm run buildAfter packaging,runtimeChunk results are verified

npm run buildAfter packaging, the results of splitChunks are verified

npm run buildAfter packaging, the cript-ext-html-webpack-plugin results are verified

5. Stats bundle Configures the terminal to output logs

Stats: The backend packaging script is deployed via Docker, so you need to configure the webpack output, otherwise the info is black and white, and it is difficult to read the logs

stats: {
    colors: true.modules: false.children: false.chunks: false.chunkModules: false
  }
Copy the code

Development environment optimization

1. Devtool debugging mode

Configure it in webpack.common.js

const merge = require('webpack-merge')
const argv = require('yargs-parser')(process.argv.slice(-3))
const mode = argv.mode || 'development'
const mergeConfig = require(`./webpack.${mode}.js`)
const common = merge(commonConfig, mergeConfig)
module.exports = common
Copy the code
devtool: 'cheap-module-eval-source-map',
Copy the code

2. devServer & HotModuleReplacementPlugin

I don’t want to go into this

plugins:[ 
    new webpack.HotModuleReplacementPlugin()
],
devServer: {
    historyApiFallback: true.overlay: {
      errors: true
    },
    // Notify file changes
    watchOptions: {
      poll: true
    },
    open: false.hot: true.proxy: {
      '/api': {
        target: 'http://localhost:10080/'.changeOrigin: true.pathRewrite: {
          '^/api': '/api'}}},host: '0.0.0.0'.port: 8000
},
Copy the code

Production environment optimization

Todo: considering the role of each plug-in zhuanlan.zhihu.com/p/102632472

1. plugins

Dist OptimizeCssAssetsPlugin: cssnano: A PostCSS plug-in that can be added to your build process to ensure that the resulting CSS stylesheet file for production is as small as possible. CompressionPlugin: Generate GZIP with compression

const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const CompressionPlugin = require('compression-webpack-plugin')

plugins: [
    new CleanWebpackPlugin(),
    new OptimizeCssAssetsPlugin({
      assetNameRegExp: /\.less$/g,
      cssProcessor: require('cssnano'),
      cssProcessorPluginOptions: {
        preset: ['default', {
          discardComments: { removeAll: true },
          normalizeUnicode: false.False is recommended, otherwise garbled characters will be generated when using Unicode-range
          safe: true // Avoid cssnano recalculating z-index}},canPrint: true
    }),
    new CompressionPlugin({
      algorithm: 'gzip'.// 'brotliCompress'
      test: /\.js$|\.html$|\.css/.// + $|\.svg$|\.png$|\.jpg
      threshold: 10240.// Compress data over 10K
      deleteOriginalAssets: false // Do not delete the original file})].optimization: {
    moduleIds: 'size'.minimizer: [
      UglifyJsPlugin (used to compress JS, built-in in WebPack4) will need to be reconfigured
      // https://www.jianshu.com/p/dd9afa5c4d0f
      new OptimizeCssAssetsPlugin({})
    ]
  },
Copy the code

In the backend project NGIx configuration

gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 5;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_disable "MSIE [1-6]\.";
gzip_vary on;
Copy the code

npm run buildAfter packaging,OptimizeCssAssetsPluginResults verification of npm run buildAfter packaging,CompressionPluginResults verification of

2. optimization

  1. How does the moduleIds persistent cache look at the effect of chunk packaging

optimization.moduleIds: 'size'

2. UglifyJsPlugin

 minimizer: [
  new UglifyJsPlugin({
    exclude: /\.min\.js$/,
    parallel: os.cpus().length,
    cache: true.sourceMap: true.uglifyOptions: {
      compress: {
        warnings: false.drop_console: true.collapse_vars: true.reduce_vars: true
      },
      output: {
        beautify: false.comments: false}}})]Copy the code

Dynamic loading of project optimization

Large components of a project can be dynamically loaded using ES6’s import(), annotating the webpackChunkName magic.

  1. Uncommonly used modal, draw and other pop-up boxes can be asynchronously delayed loading of these components and be removed from the code loaded from the first screen
  2. Route lazy loading
{
  path: '/productsManagement/allProducts'.name: 'AllProducts'.component: () = > import(
    /* webpackChunkName: `AllProducts` */
    /* webpackMode: "lazy" */
    '@productsManagement/allProducts/DyProductList'),
  meta: {
    keepAlive: true.requiresAuth: true}},Copy the code

Results contrast

Packing speed

Before optimization, the first packaging time of production environment: 49038msBefore optimization, the second packaging time of the production environment: 70113msAfter optimization, the first packaging time of production environment: 47663msAfter optimization, the second packaging time of production environment: 13738ms,A 70% increase

Display the size of resources after packaging

The following is the collation file. The production package without Gzip will be packed after subcontracting. The smaller package will be loaded using gzip

Static resource collation

icon

I wrote an icon to form vue-midou-icon. This component supports iconFONT platform connection, without the need to manually download icon. The usage is as follows:

/ / register
import MdUi from 'vue-midou-icon'
const IconFont = MdUi.createFromIconfontCN({
  scriptUrl: ['your-iconfont-symbbol-url'].// Name can be left blank. Default is mD-icon
  name: 'your-iconfont-component-name',
})
Vue.use(IconFont)


// use the following command
<your-iconfont-component-name type="iconType" class="className">
</your-iconfont-component-name>
Copy the code

images

  1. The image needs to be compressed, compress the address. You can compress the image multiple times according to its actual size.
  2. When the picture is small. You can consider putting it locally. Packaging is done in the webPack configuration
  3. If the image is a GIF. Consider having the UI capture images frame by frame before preloading them. How to do preloading can refer to the page image preloading and lazy loading strategy
  4. Long list images use element – UI images for lazy loading

<el-image v-for="url in urls" :key="url" :src="url" lazy></el-image>

fonts

Our project had online image editing, so we had to load a lot of UI fonts. Each font package is 17M long before conversion. Convert OTF to WOFF by compressing the conversion font. It’s going to be 5 kilobytes, but it’s going to be a little bit different. Click to enter the font compression address

css

  1. CSS variable specification, using style-resources-loader global injection
  2. The common CSS is arranged separately and the unified entrance is introduced
  3. Unified folder of service CSS and formation

Code optimization

Catalog arrangement and module division

The common components of the original directory structure are not separated from the page business components, so there are more and more components, and there are routes to load components directly in the components. The directory structure is out of order. There is no concept of module division.

Public logic is removed createLoading, modelExtengs, listModel, baseModel

  1. ModelExtend dVA-model-extend of class DVA, click the name to view the code
  2. CreateBaseModel List request filter and query encapsulation, click the name to view the code
  3. CreateLoadingPlugin The loading middleware of action in dVA. Click the name to view the code

createLoadingPluginVuex is a vuex plug-in that adds loading to each action. Set loading to true before it starts and false after it ends

Automatic registration with require.context

const requireDirectives = require.context(
  '@/dirname'.false./([\w\W]*)\.(vue|js)$/
)

export const registerDirectives = () = >
  requireDirectives.keys().forEach(fileName= > {
    const directiveConfig = requireDirectives(fileName)
    const directiveName = fileName.split('/').pop().replace(/\.\w+$/.' ')
    Vue.directive(
      directiveName,
      directiveConfig.default || directiveConfig
    )
  })
Copy the code

Please thumb up

The side development side does, the personal feeling still has a lot of imperfect place. I hope you can see the generous comments, grateful. If feel good, beg point praise, thank you big guy !!!! 💗