In the last six months, I was maintaining a management background project of the company. At the beginning of construction, the technology stack was quite chaotic. The construction scheme adopted the method of calling Webpack in Gulp, in which Gulp was responsible for processing. In this set of construction scheme, there are mainly these problems:

  1. JS compression, CSS compatibility and other functions are not implemented.
  2. In development mode, the code is saved, the project is completely repackaged, and the continuous build is not only slow, but also cacheable (page refresh changes do not take effect after the build is finished).
  3. Because the current program is not usedhttp-proxy-middlewareSuch request proxy module leads to the deployment of back-end services when the project is developed locally, which is unfriendly to new developers and often causes code synchronization problems between the test environment and the local environment due to delayed communication.

Therefore, after getting familiar with the project, I plan to upgrade its build scheme, mainly to solve the above problems.

1. Description of the original construction scheme

Original build speed

  • npm run build: Pack about 50 PCS
  • npm run dev: Enable the development mode for about 50s, save the automatic recompilation takes about 6s, after the compilation is complete, you need to refresh to see the effect, occasionally due to cache problems, you need to automatically recompile again to see the effect

Original construction results

  • ./build/development: Stores rendered JS files
  • ./build/html: Stores the rendered HTML file
  • ./build/rev: A JSON file that stores the hash value of each entry file

Package code parsing

/** * Use the gulp-clean plugin to delete files in the build directory */
gulp.task('clean'.function () {
    if(! stopClean) {return gulp.src('build/' + directory, { read: false }).pipe(clean())
    }
})
/** * Use webpack to package vue and JS files, */ after clean
gulp.task('webpack'['clean'].function (callback) {
    deCompiler.run(function (err, stats) {
        if (err) throw new gutil.PluginError('webpack', err)
        gutil.log('[webpack]', stats.toString({}))
        callback()
    })
})
/** * Use gulp-uglify to uglify js files after webpack
gulp.task('minify'['webpack'].function () {
    if (environment) {
        return
    } else {
        return gulp.src('build/' + directory + '/*.js').pipe(uglify())
    }
})
/** * What does gulp-rev do when run after minify? * Generate MD5 signature according to static resource content, packaged file name will add MD5 signature, and generate a JSON to save file name path correspondence. * Replace static resource paths in HTML with file paths with MD5 values so that HTML can find resource paths. * Some people might do this: Static servers set the expiration time of static resources to never expire. 304 * Version update only updates the content of the modified static resource * does not delete the static resource of the old version, only updates the HTML when version rollback, also does not increase the number of HTTP requests */
gulp.task('hashJS'['minify'].function () {
    var dest = gulp.src(['A string of entry files... '])
        .pipe(rev()) // Set the hash key for the file
        .pipe(gulp.dest('build/' + directory)) // Write out the piped file to the directory
        .pipe(rev.manifest({})) // Generate JSON to map hash keys
        .pipe(gulp.dest('build/rev')) // Write out the piped file to the directory! environment && gulp.src(['A string of entry files... ']).pipe(clean())
    return dest
})
/** * Use gulp-rev-replace to replace the js and CSS referenced in HTML with a new hash */ Use gulp-livereload to partially update the page after all files have been repackaged */
gulp.task('revReplace'['hashJS'].function () {
    return gulp.src(['html/*.html'])
        .pipe(revReplace({ ... })) // Provide a new hash for js references in HTML
        .pipe(gulp.dest('build/html')) // Output file
        .pipe(livereload()) // Update the page locally
})
/** * With gulp.watch, when any files in the application directory change, execute the package command * gulp.watch: monitor the files and do something when the files change. * /
gulp.task('watch'['revReplace'].function () {
    stopClean = true
    livereload.listen()
    gulp.watch('app/**/*'['clean'.'webpack'.'minify'.'hashJS'.'revReplace'])})/** * Outputs dev and build workflows */
gulp.task('default'['clean'.'webpack'.'minify'.'hashJS'.'revReplace'.'watch']) // dev
gulp.task('build'['clean'.'webpack'.'minify'.'hashJS'.'revReplace']) // build
/** * Webpack configuration */
var devCompiler = webpack({
    entry: {...// a bunch of entry files
        vendor: ['vue'.'vue-router'.'lodash'.'echarts'] // Public module
    },
    output: {
        path:... .// The destination path of all output filespublicPath: ... .// Output the directory of the parsed filefilename: ... .// Output file
        chunkFilename: ... // File requested asynchronously
    },
    // Remove the following contents from the bundle and reduce the file size
    external: {
        jquery: 'jQuery'.dialog: 'dialog'
    },
    plugins: [
        /** * By stripping out the public module, the resultant file can be loaded once at the beginning and stored in the cache for later use. * This leads to an increase in page speed because browsers quickly remove common code from the cache, rather than loading a larger file every time a new page is visited. * /
        new webpack.optimize.CommonsChunkPlugin({
            name: ['vendor']}),/** * DefinePlugin allows you to create a global constant that can be configured at compile time. This can be very useful when the development mode and production mode are built to allow different behaviors. * /
        new webpack.DefinePlugin({
            __VERSION__: new Date().getTime()
        })
    ],
    resolve: {
        root: __dirname,
        extensions: [' '.'.js'.'.vue'.'.json'].// Parse the file suffix whitelist for the component
        alias: { ... } // Configure the path alias
    },
    module: {
        // Loaders for each file
        loaders: [
            { test: /\.vue$/.loader: 'vue-loader' },
            { test: /\.css$/.loader: 'style-loader! css-loader' },
            { test: /\.jsx$/.loader: 'babel-loader'.include: [path.join(__dirname, 'app')].exclude: /core/ },
            { test: /\.json$/.loader: 'json'}},vue: {
        loaders: {
            js: 'babel-loader'}}})Copy the code

2.GulpFunction to move toWebpack1Performed on the

usehtml-webpack-pluginThe plug-in build project owner.htmlfile

module.exports = {
    plugins: [
        new HtmlWebpackPlugin({
            filename: '... '.// Output path
            template: '... '.// Extract the path to the source HTML
            chunks: ['... '].// The module to import
            inject: true // Whether to append to the bottom of the body}})]Copy the code

usewebpack.optimize.UglifyJsPluginplug-insJSThe compression

module.exports = {
    plugins: [
        new webpack.optimize.UglifyJsPlugin({
            compress: { warnings: false}}})]Copy the code

usewebpack-dev-serverModule, providingnodeSet up the development environment

module.exports = {
    devServer: {
        clientLogLevel: 'warning'.// Log output level. The log output level is set to a level higher than warning
        inline: true.// Enable Live Reload
        hot: true.// Enable hot overloading
        compress: true.// Gzip all static resources
        open: true.// By default, the browser is opened when the local service is started
        quiet: true.// Disable verbose build logshost: ... .// Service startup domain nameport: ... .// The port on which the service starts
        proxy: { ... }, // HTTP proxy configuration
        /** * This configuration is often used to solve the problem of matching all 404 routes back to index.html in SPA h5 routing mode. * Since the production environment matches a simple alias for the home page, the development environment also copies the configuration of the backend service */
        historyApiFallback: {
            rewrites: [{ from: '/^\/admin/'.to: '... '}}}}]Copy the code

Hit the pit

  1. [email protected] requires a peer of webpack^@4.0.0 but none is installed.: The two module versions are incompatible, go back towebpack-dev-server@2Succeeded.
  2. Cannot resolve module 'fsevents' ...: will be globalwebpackCall instead directly fromnode_modules/webpackCall directly to solve the problem,node node_modules/webpack/bin/webpack.js --config webpack.config.js.
  3. Cannot resolve module 'fs' ...Configuration:config.node.fs = 'empty'forWebpackprovidenodeNative module that allows it to be loaded into this object.
  4. Thermal overload only.jsand.cssand.vueIn the<style>Inside style works, right.vueIn the filehtmlThe template andjsThe message “module code has been changed and recompiled, but hot overloading does not take effect, and the global refresh policy may be enabled” will be printed. It has not been solved for the time being, and the initial assessment is that the version is lowvue-hot-reload-apiIf you have any questions about these parts, please explain them in the comments section.

From 3.Webpack1Upgrade to theWebpack3

Since Webpack2 is almost completely compatible with Webpack3 and only involves some incremental functionality, choose to migrate directly from Webpack1 to Webapck3. Install Webpack3 in your project first, and then follow the section “Migrating from Webpack1” in the Webpack2 documentation. To make changes to the configuration item, refer to this document: www.html.cn/doc/webpack…

There were no problems with the upgrade and it worked with a few changes to the documentation configuration. Take a look at the features implemented so far:

  1. The newWebpackThe build code already implements all of the original functionality, as listed below.
  2. usewebpack-dev-serverAs a development server, save time is implementedlive reloadThe function.
  3. usehttp-proxy-middlewarePlugins, which proxy requests directly to the test server, separate the development environment from locally deployed back-end services, greatly reducing the time cost of development environment deployment.
  4. newfriendly-errors-webpack-plugin, output friendly build logs, print the development environment addresses of several important modules, complete configuration referencevue-cli@2Is the default configuration.
  5. newpostcss-loader, to add CSS compatibility processing, complete configuration referencevue-cli@2Is the default configuration.
  6. usewebpack.optimize.UglifyJsPluginCompress js code.

Try to build and print the build time record:

  • npm run build: about135s
  • npm run dev: Initial build about58sContinue to build about30s

The project took too long to build (the first time I packed it scared me…) , can only continue to seek optimization on the construction speed

In 4.Webpack3To optimize the construction speed

usewebpack-jarvisMonitor build performance

Webpack-jarvis is a graphical webpack performance monitoring tool that is easy to configure and has specific outputs for the time ratio of the build process, as well as a detailed record of the build results

// After a simple configuration, you can output the build result record on local port 3001
const Jarvis = require('webpack-jarvis')
module.exports = {
    plugins: [
        new Jarvis({
            watchOnly: false.port: 3001}})]Copy the code

usehappypack

The happypack module uses the multi-process model to speed up code construction. However, after using it, there seems to be no obvious results. The build time is probably reduced by a few seconds. I’m not sure what the effect of this module is on the optimization of scenes. I read an article about happypack principle before, but I haven’t read it carefully. If you are interested in it, you can study it. Taobaofed.org/blog/2016/1…

const HappyPack = require('happypack')
const os = require('os')
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length })
module.exports = {
    plugins: [
        new HappyPack({
            // The id of the happypack, which needs to be declared when called. If you want to compile other types of files, you need to declare another happypack
            id: 'js'.// cacheDirectory: When set, we will try to use the results of the cache loader when Babel is compiled to avoid the costly reworking of Babel
            use: [{ loader: 'babel-loader'.cacheDirectory: true}].// Determine how many process pools need to be split based on the number of CPU cores
            threadPool: happyThreadPool,
            // Whether to output logs about the compilation process
            verbose: true}})]Copy the code

After this step, print the build time log:

  • npm run build: about130s
  • npm run dev: Initial build about60sContinue to build about30s

devtoolConfigured tocheap-module-eval-source-map

The devtool option enables the cheap-module-eval-source-map mode: vue-cli@2 defaults to this mode, with cheap indicating that column information is omitted when output source-map; Module indicates that a precompiler such as babel-loader is enabled during compilation so that uncompiled source code can be seen directly during debugging; Eval means to enable eval mode compilation. This mode directly uses the eval function to execute the string of the compiled module, reducing the step of converting the string into an executable code file and speeding up reconstruction in project development. Source-map represents the mapping table of the output source code, so that errors can be directly located to the source code during development, improving development efficiency.

After this step, the effect is not obvious =.= (compared to the original source-map), about a few seconds less, output the build time record:

  • npm run build: about130s
  • npm run dev: Initial build about58sContinue to build about30s

usehtml-webpack-plugin-for-multihtmlSpeed up reconstruction of multi-entry projects

It takes 30 seconds to rebuild! Various searches found an issue of the HTML-webpack-plugin and found that html-webpack-plugin@2 does have a noticeable slowdown in building multi-entry applications because the build content is not successfully cached so that all code is recompiled with each rebuild. The solution presented by the author is to use a branch project of this module (a project forked by the author and fixed for this problem) html-webpack-plugin-for-multihtml, which uses exactly the same usage as html-webpack-plugin. It only takes about 1s to rebuild after use.

After this step, print the build time log:

  • npm run build: about130s
  • npm run dev: Initial build about58sContinue to build about1s

usewebpack.DllPluginExtract common modules

I found a lot of big dependency packages in the output, such as Vue core library, Lodash, Echarts, etc., and some static resources that do not want to be packaged. I wanted to avoid compiling these content every time and improve the compilation speed, so I found this plug-in.

The webpack.DllPlugin plugin comes from a.dll file (dynamic link library) for Windows. First, a package containing public modules and a mapping table are constructed by the DllPlugin module, and then corresponding dependencies are associated with each module through the mapping table by the DllReferencePlugin module. In this way, these public modules can be pre-packaged, so that there is no need to deal with these modules in the future construction, reducing the packaging time.

// webpack.dll.conf.js
const webpack = require('webpack')
module.exports = {
    entry: {
        vendor: [...]. },output: {
        path: resolve('build/development'),
        filename: '[name].dll.js'.library: '[name]_library'
    },
    plugins: [
        new webpack.optimize.UglifyJsPlugin(),
        new webpack.DllPlugin({
            path: resolve('build/development/[name]-manifest.json'), // Generate the location and file name of the manifest file output
            name: '[name]-library'.// This is the same as output.library, which corresponds to the name field in the manifest.json file to prevent global variable conflicts
            context: __dirname
        })
    ]
}
// webpack.base.conf.js
const webpack = require('webpack')
module.exports = {
    plugins: [
        new webpack.DllReferencePlugin({
            context: __dirname,
            manifest: require('.. /build/development/vendor-manifest.json') // Let WebPack get the used dependencies from the mapping table}})]Copy the code

Once packaged, the common library vendor.dll.js file needs to be included in the HTML file

<html>
    <head></head>
    <body>
        <div id="app"></div>
        <script src="/build/development/vendor.dll.js"></script>
        <! Other JS should be injected at the back of the DLL to ensure that the contents of the public library can be referenced.
    </body>
</html>
Copy the code

After this step, output the build time log and see a significant increase in build efficiency:

  • npm run dll: about25s
  • npm run build: about70s
  • npm run dev: Initial build about55sContinue to build about1s

5. Afterword.

The optimization is almost over here. This optimization provides the old project with some functions of the new generation SPA project, and builds a more modern local development environment. Because this article is a bit too long, the complete configuration is left in another article.

6. Q&A

Q: why not just upgrade to Webpack4?

A: Webpack4 only supports vue-loader@15 or earlier versions, but this version cannot parse Vue1 files.

The author information

Other Articles by author

  • (1) 【JavaScript】
  • 【 Interview 】 Social recruitment intermediate front-end written interview questions summary – answers and development
  • What is cross-domain, why do browsers prohibit cross-domain, and the divergent learning it causes