webpack4自定义脚手架优化

在上篇中搭建了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

splitChunks 提取公用代码

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

14

compression-webpack-plugin

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

15

总结

对常见的优化方案做了一次测试和实践,如果有其他好的优化方法,可以留言分享。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容