webpack4自定义脚手架构建优化

1,074 阅读8分钟

在上篇中搭建了vue的基本脚手架,为了加快脚手架打包项目的构建速度和减少打包代码的体积,下面在上一篇的基础上添砖加瓦,完成脚手架的优化。

脚手架目录

脚手架是在原有的基础上构建的,所以基本的目录结构相类似。不过加上了对JS压缩,CSS代码压缩(tree shake),引入了dllPlugin打包不常变动的类库生成动态链接库,加入happypack开启多进程打包,加快构建的速度,下面也会说到运用npm script,通过sftp把打包的文件上传到部署的目录,避免手动上传。

脚手架地址:https://github.com/Harhao/webpack/tree/dev

|-- .gitignore
|-- build
|   |-- webpack.base.conf.js
|   |-- webpack.dev.conf.js (开发环境打包配置)
|   |-- webpack.dll.conf.js (生成dll动态链接库)
|   `-- webpack.prod.conf.js(生产环境打包配置)
|-- config
|   `-- index.js (打包配置参数)
|-- dist (生产环境打包目录)
|   |-- index.html
|   |-- precache-manifest.2df00ef5798fdac7218a2cecb8233a17.js(缓存清单)
|   |-- service-worker.js(service-worker文件)
|   `-- static
|       |-- css
|       |   `-- main_de7bb505.css
|       `-- js
|           |-- framework_2f05dfbce1b06dd8.js
|           |-- framework_2f05dfbce1b06dd8.js.gz (开启gz压缩打包结构)
|           |-- main_2f05dfbce1b06dd8.js
|           `-- vendors~main_2f05dfbce1b06dd8.js
|-- dll(开启dllplugin打包的链接库)
|   |-- axios.dll.js
|   |-- axios.manifest.json
|   |-- framework.dll.js
|   `-- framework.manifest.json
|-- package.json
|-- postcss.config.js
|-- public
|   `-- index.html
`-- src(开发主目录)
    |-- App.vue
    |-- main.js
    |-- router
    |   `-- index.js
    `-- views
        |-- admin
        |   `-- index.vue
        `-- login
            `-- index.vue

开发环境配置

webpack.dev.conf.js开发环境配置文件中,增加了happypackDllplugin动态链接。happypack主要是开启多进程打包文件,加快打包构建速度。而Dllplugin主要是对一些不常变动的类库提取打包,在再次编译打包过程,可以免除再次的打包。

开发环境配置文件

下面是webpack.dev.conf.js开发环境全瞰,主要增加了DllReferencePluginhappypackadd-asset-html-webpack-plugin插件。下面对各自的作用详细说明。

const path = require("path")
const HappyPack = require("happypack")
const VueLoaderPlugin = require("vue-loader/lib/plugin")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
const AddAssetHtmlWebpackPlugin = require("add-asset-html-webpack-plugin")
const HotModuleReplacementPlugin = require("webpack/lib/HotModuleReplacementPlugin")
const config = require("../config/index")

module.exports = {
    mode: "development",
    devtool: "cheap-module-eval-source-map",
    entry: path.resolve(__dirname, "../src/main.js"),
    output: {
        filename: "[name]_[hash:16].js",
        path: path.resolve(__dirname, "../dist"),
        publicPath: '/'
    },
    resolve: {
        modules: ["node_modules"],
        alias: {
            "@": path.resolve(__dirname, '../src')
        },
        extensions: [".js", ".json", ".vue", ".scss"]
    },
    module: {
        rules: [{
            test: /\.js$/,
            use: ["happypack/loader?id=babel"],
            exclude: path.resolve(__dirname, '../node_modules')
        },
        {
            test: /\.scss$/,
            use: ["style-loader", "css-loader", "postcss-loader", "sass-loader"]
        }, {
            test: /\.vue$/,
            use: ["vue-loader"]
        }, {
            test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
            use: ["happypack/loader?id=url"]
        }]
    },
    plugins: [
        new DllReferencePlugin({
            manifest: require('../dll/framework.manifest.json')
        }),
        new DllReferencePlugin({
            manifest: require('../dll/axios.manifest.json')
        }),
        new HappyPack({
            id: 'babel',
            loaders: ["babel-loader?cacheDirectory"]
        }),
        new HappyPack({
            id: 'url',
            loaders: ["url-loader"]
        }),
        new VueLoaderPlugin(),
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, "../public/index.html"),
            filename: 'index.html',
            inject: 'body'
        }),
        new AddAssetHtmlWebpackPlugin({
            filepath: path.resolve(__dirname, '../dll/framework.dll.js') // 对应的 dll 文件路径
        }),
        new AddAssetHtmlWebpackPlugin({
            filepath: path.resolve(__dirname, '../dll/axios.dll.js') // 对应的 dll 文件路径
        }),
        new HotModuleReplacementPlugin()
    ],
    devServer: {
        ...config.dev.server
    },
    /**
     * 开启webpack对文件变动的监听
     */
    watch: true,
    watchOptions: {
        ignored: /node_modules/,
        aggregateTimeout: 300,
        poll: 1000
    }
}

DllPlugin动态链接库

.dll 为后缀的文件,这些文件称为动态链接库。提供为其他模块使用的函数和数据。在一些不常变动的类库vuejsvue-routervuex,我们可以打包成动态链接库,动态链接库只需要编译一次,在之后的构建过程中被动态链接库包含的模块将不会在重新编译。加快我们构建的速度。配置dll动态链接库,需要另外一个配置文件webpack.dll.conf.js

Dllplugin 插件专门用于单独的webpack配置中,以创建仅限dll的捆绑包。它创建了一个manifest.json文件,用于[DllReferencePlugin]映射依赖项。下面webpack.dll.conf.js通过配置生成dll动态链接库。

1

通过配置npm script生成动态链接库文件,在package.json定义了npm run build:dll打包生成dll动态链接库。

2

生成下面的dll动态链接库,在运行开发环境前,需要先打包生成dll动态链接库,这样开发环境打包时会自动引用动态链接库。其中frameworkaxios都是我们自定义命名,menifest是类库的映射文件。

3

有动态链接库,还需要在webpack.dev.conf.js文件中对链接库的引入。DllReferencePlugin 会去 manifest.json 文件读取 name字段的值, 把值的内容作为在从全局变量中获取动态链接库中内容时的全局变量名。而AddAssetHtmlWebpackPlugin是将JavaScript或CSS资源添加到生成的HTML中,这里使用该插件将dll动态链接库添加到index.html中。

4

生成index.html源码已经加入framework.dll.jsaxios.dll.js,源码结构如下所示:

5

happypack多进程打包

HappyPack通过并行转换文件使得初始webpack构建更快。happypack通过开启多个子进程并行打包文件,使文件构建速度明显加快。在使用happypack过程中,发现happypackscss支持度不高。所以在webpack.dev.conf.js没有对scss文件进行处理。在happypack的官网案例中,happypackless的支持度比较高。happypack使用比较简单,如下所示,下面对以js为后缀的文件用happypack进行打包。happypack默认开启的进程是3个,可以自定义配置,详细参数可以参照官方文档说明。

6

开启happypack成功打包的构建流程图

7

生产环境配置

webpack.prod.conf.js生产配置文件中,添加了workbox-webpack-plugin渐进行PWA的资源缓存;optimize-css-assets-webpack-plugincss代码压缩处理;compression-webpack-plugin对大文件进行gz压缩;UglifyJsPluginjs文件压缩处理,UglifyJsPlugin在上篇已做说明。

生产环境配置文件

webpack.prod.conf.js生产配置环境文件,没有对不常更改的类库(vuejs)生成动态链接库。利用webpack4内部提取公共代码,进行了处理。总瞰如下所示:

const path = require("path")
const VueLoaderPlugin = require("vue-loader/lib/plugin")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const { CleanWebpackPlugin } = require("clean-webpack-plugin")
const UglifyJsPlugin = require("uglifyjs-webpack-plugin")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const WorkboxPlugin = require('workbox-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const CompressionWebpackPlugin = require('compression-webpack-plugin')
module.exports = {
    mode: "production",
    entry: {
        main: path.resolve(__dirname, "../src/main.js"),
        framework: ["vue", "vue-router", "vuex"]
    },
    output: {
        filename: "static/js/[name]_[hash:16].js",
        path: path.resolve(__dirname, "../dist"),
        publicPath: './'
    },
    resolve: {
        modules: ["node_modules"],
        alias: {
            "@": path.resolve(__dirname, '../src')
        },
        extensions: [".js", ".json", ".vue", ".scss"]
    },
    module: {
        rules: [{
            test: /\.js$/,
            use: [{
                loader: "babel-loader",
                options: {
                    presets: ['@babel/preset-env']
                }
            }],
            exclude: /node_modules/
        },
        {
            test: /\.scss$/,
            use: [
                {
                    loader: MiniCssExtractPlugin.loader,

                },
                "css-loader",
                "postcss-loader",
                "sass-loader"
            ],
            exclude: /node_modules/
        },
        {
            test: /\.vue$/,
            use: ["vue-loader"],
            exclude: /node_modules/
        }]
    },
    plugins: [
        new VueLoaderPlugin(),
        new HtmlWebpackPlugin({
            title: '自搭建webpack脚手架',
            template: path.resolve(__dirname, "../public/index.html"),
            filename: 'index.html',
            inject: 'body',
            minify: {
                removeComments: true,//移除注释
                collapseWhitespace: true,//移除空白字符串
                removeAttributeQuotes: true //移除双引号
            }
        }),
        new CleanWebpackPlugin(),
        new MiniCssExtractPlugin({
            filename: 'static/css/[name]_[chunkhash:8].css',
            chunkFilename: '[id].css',
        }),
        new WorkboxPlugin.GenerateSW({
            clientsClaim: true,
            skipWaiting: true
        }),
        new CompressionWebpackPlugin({
            filename: '[path].gz[query]',
            algorithm: 'gzip',
            test: /(\.js$|\.css$)/,
            threshold: 10240,
            minRatio: 0.8
        })
    ],
    optimization: {
        usedExports:true,
        minimizer: [
            new UglifyJsPlugin({
                test: /\.js(\?.*)?$/i
            }),
            new OptimizeCSSAssetsPlugin({
                // cssProcessorOptions: true? {map: { inline: false }}:{}
            })
        ],
        splitChunks: {
            chunks: "all",
            minChunks: 1,
            minSize: 0,
            cacheGroups: {
                framework: {
                    test: "framework",
                    name: "framework",
                    enforce: true
                }
            }
        }
    }
}

workbox-webpack-plugin

渐进式Web应用程序(或PWA)使我们开发的应用在离线后,依然可以访问。主要依靠了Service Workers的Web技术实现。在webpack社区提供了workbox-webpack-plugin方便实现PWA功能。

8

wenbpack.prod.conf.js配置好插件workbox-webpack-plugin后,需要在项目的入口文件里注册serviceWorker。这里展示的vuejs的入口文件main.js

9

运行生产环境打包脚本命令,有2个额外的文件正在生成; service-worker.jsprecache-manifest.18d731d6b692ffdc910ac5548db2f8c0.jsservice-worker.jsService Worker文件,precache-manifest.18d731d6b692ffdc910ac5548db2f8c0.js是一个service-worker.js需要运行的文件。您自己生成的文件可能会有所不同; 但你应该有一个service-worker.js文件。

10

接下来要离线环境进行测试,查看service-worker离线缓存的效果。webpack官网提供了一个http-serverservice-worker测试。

11

service worker已经生效,断开http-server服务,刷新网页,依旧可以正确显示到需要的资源。

12

optimize-css-assets-webpack-plugin

optimize-css-assets-webpack-plugin主要是对打包的css文件进行压缩处理。在webpack4版本中,设置mode模式为production,会自动对js代码进行压缩处理,其实底下是用uglifyjs-webpack-plugin进行压缩。但是css文件还是需要手动进行压缩。而optimize-css-assets-webpack-plugin起的就是压缩css作用。

打包压缩的文件如下所示,可见css样式是压缩状态的。

.entry[data-v-7ba5bd90]{color:red;-webkit-transform:scale(.8);-ms-transform:scale(.8);transform:scale(.8)}.admin[data-v-2ba5fe30]{color:#333}

optimize-css-assets-webpack-pluginwebpack.prod.conf.js配置使用:

13

postcss

postcss是一个用JavaScript 工具和插件转换 CSS代码的工具。postcss加上autoprefixer帮助我们在新的css3属性自动添加厂商前缀。

package.json文件添加以下字段:

 "browserslist": [
    "last 5 version",
    ">1%",
    "ie>=8"
  ]

在项目的根目录下,创建postcss.config.js,内容如下:

module.exports = {
  plugins: {
    autoprefixer: {
    }
  }
};

JS tree shake

webpack4中使用tree shake剔除没有使用的JS代码比较简单,直接在optimization中开启usedExports,不过JS tree shake只对ES6语法有效,所以要度量使用。关于对于csstree shake,有朋友建议是purecss对没有使用的css代码剔除,不过实验了一下,感觉没有效果,就没有做推荐。

13

webpack-deploy-sftp

在打包生成生产环境的项目中,想自动上传打包目录到开发测试环境预览,可以利用webpack-deploy-sftp插件集成到webpack.prod.conf.js中,打包完成会自动上传到linux开发环境中。

17
如果想通过自定义npm script的方法,可以自己编写上传脚本,下面是自己编写的一个参考:

const Client = require("ssh2-sftp-client")
const fs = require('fs')
const path = require('path')
//有关账号密码端口设置文件
const config = require('./config')
//本地需要上传的目录
const filePath = './dist'
//远程地址
const remotePath = '/home/xx/xx'
const sftp = new Client()
let tmpPath = ''
sftp.connect(config).then(() => {
    console.log("连接成功")
    fileTravel(filePath)
})
sftp.on('end', () => {
    console.log('end event')
})
sftp.on('close', () => {
    console.log('close event')
})
function fileTravel(filePath) {
    fs.readdir(filePath, function (err, files) {
        if (err) {
            console.log(err)
        } else {
            files.forEach(function (filename) {
                const filedir = path.join(filePath, filename)
                console.warn(`upload file: ${filedir}`)
                let url = filedir.substr(filedir.indexOf('\\') + 1)
                url = url.replace(/\\/g, '/')
                fs.stat(filedir, function (error, stats) {
                    if (error) {
                        console.error(`获取文件stats失败`)
                    } else {
                        const isFile = stats.isFile()
                        const isDir = stats.isDirectory()
                        if (isDir) {
                            tmpPath = `${remotePath}/${url}`
                            sftp.mkdir(tmpPath, true).then(() => {
                                fileTravel(filedir);
                            }).catch(err => {
                                fileTravel(filedir)
                            })
                        }
                        if (isFile) {
                            sftp.put(filedir, `${remotePath}/${url}`)
                        }
                    }
                })
            })
        }
    })
}

定义npm script命令,执行上传打包的文件夹。

18

splitChunks 提取公用代码

webpack.dev.conf.js开发环境中使用了dllPlugin的动态链接库方法提取公用的类库,加快构建速度。在生产环境中,采用的是webpack4内置功能提取公用代码。在webpack3中采取CommonChunkPlugin提取公共模块和第三方库。在webpack4已经不建议使用该插件,而是使用内置的方法。

14

compression-webpack-plugin

在上面使用splitChunks提取公共模块framework,但是打包出来的模块还是比较大,在浏览器下载包模块时候时间会比较长,所以在对打包出来,体积比较大的模块使用compression-webpack-plugin打包成gz包。配合后端开启gz支持,这样可以加快资源下载的速度。

15

webpack-merge

webpack.dev.conf.jswebpack.prod.conf.js文件的公用部门提取到webpack.base.conf.js中,通过webpack-merge合并不同部分。

webpack.base.conf.js

webpack.base.conf.js公用部份如下:

16

webpack.dev.conf.js

wepack.dev.conf.js剔除公用的部分,其他配置项如下所示:

17

webpacck.prod.conf.js

wepack.prod.conf.js剔除公用的部分,其他配置项如下所示:

18

如果喜欢可以给个赞~或星~哟
GitHub地址: github.com/Harhao/webp…