webpack4 打包优化

1 参考文章

彻底解决 webpack 打包文件体积过大

webpack4提升180%编译速度

详解webpack4之splitchunksPlugin代码包分拆

webpack v4 中的断舍离

开发工具心得:如何 10 倍提高你的 Webpack 构建效率

Webpack打包构建太慢了?试试几个方法

上手webpack4并进阶?来看这里~

 

注意合并 webpack.config.js 文件的时候,把 commonConfig 放在前面 否则一些地方会报错
module.exports = merger(commonConfig,prodConfig);

2: webpack4中的 optimization.runtimeChunk的作用是什么?

首先看参考文章:
优化持久化缓存的, runtime 指的是 webpack 的运行环境(具体作用就是模块解析, 加载) 和 模块信息清单,
模块信息清单在每次有模块变更(hash 变更)时都会变更, 所以我们想把这部分代码单独打包出来,
配合后端缓存策略, 这样就不会因为某个模块的变更导致包含模块信息的模块(通常会被包含在最后一个 bundle 中)缓存失效.
optimization.runtimeChunk 就是告诉 webpack 是否要把这部分单独打包出来.

假设一个使用动态导入的情况(使用import()),在app.js动态导入component.js

const app = () =>import('./component').then();

build之后,产生3个包。

0.01e47fe5.js
main.xxx.js
runtime.xxx.js

其中runtime,用于管理被分出来的包。下面就是一个runtimeChunk的截图,可以看到chunkId这些东西。

...

function jsonpScriptSrc(chunkId) {
/******/ return __webpack_require__.p + "" + ({}[chunkId]||chunkId) + "." + {"0":"01e47fe5"}[chunkId] + ".bundle.js"
/******/ }

...

如果采用这种分包策略

当更改app的时候runtime与(被分出的动态加载的代码)0.01e47fe5.js的名称(hash)不会改变,main的名称(hash)会改变。
当更改component.js,main的名称(hash)不会改变,runtime与 (动态加载的代码) 0.01e47fe5.js的名称(hash)会改变。

总结一下:

runtime.js文件相当于动态文件的索引文件,相当于一个文件夹中的index索引文件,告诉main.js要引用的文件的名字

这样app.js 变化的时候 由于不影响 componment.js 所以生成的0.01.js 和runtime.js 不会发生变化;

当 componment.js 发生变化的时候,生成的0.01.js要发生变化,同时索引文件 runntime.js 也会发生变化。但是main.js引用的是runntime.js 则不会发生变化

---

3  使用 splitchunksPlugin 提取公共代码

optimization:{
    splitChunks: {
        chunks: 'all',//同步异步全都打包
        minSize: 30000,//打包的库或者文件必须大于这个字节才会进行拆分
        minChunks: 1,//规定当模块在生成的js文件(trunk)中被调用过多少次的时候再进行拆分
        maxAsyncRequests: 5,
        maxInitialRequests: 3,
        automaticNameDelimiter: '~',//如果不写filename 默认名字 组名~[name]
        name: true,
        cacheGroups: {//缓存组,因为需要打包完成之后,在把所有要拆分的代码合并拆分,所以先要缓存
        vendors: {
            test: /[\\/]node_modules[\\/]/, //如果上面chunks定为all,就是找到所有的import文件,看他是不是调用于 node_modules 文件夹 是的话就拆分
            priority: -10,//优先级 比如同时符合vender 和 default 这个优先级高 所以存在这里
            filename: 'vendors.js', //拆分后打包的文件名字
        },
        default: {//像文件中 import进来的文件 如果不在 node_modules文件夹中 则走默认组,打包出的文件名字是 common.js
            priority: -20,
            minChunks: 2,
            reuseExistingChunk: true,//比如a.js 引用了 b.js;如果b.js在之前已经被拆分过,则这里不再对其进行拆分
            filename: 'common.js'
        }
        }
    }
}

 为了支持异步js,

npm install --save-dev @babel/plugin-syntax-dynamic-import

记得修改 .babelrc

{
  "presets":[
    ["@babel/preset-env",{
    "useBuiltIns":"usage",
    "corejs":2,
    "targets":{
        "browsers":[">1%","last 2 version","not ie <= 8"]
      }
    }]
  ],
  "plugins": ["@babel/plugin-syntax-dynamic-import"]
}

 

注意:

minSize: 指的是打包前的文件大小,并且指的是 组成 commonjs的所有文件的和的大小,而不是其中一个文件的大小;
比如 组成commonjs的三个子组件是 a.js b.js c.js 分别是10k,则minSize需大于 30k 才不会打包这几个文件,
而不是 10k。

1: chunks: "initial"-- 表示只从入口模块进行拆分
1.1 有异步引入的js文件:三个入口文件+公共的common文件+子组件【相当于原来的文件分别单独打包】
1.2 全是同步引入js文件:三个入口文件+公共的common文件{包括了子组件}

2: chunks: "async" -- 表示只从异步加载得模块(动态加载import())里面进行拆分
2.1 有异步引入的js文件:三个入口文件【common文件被重复打包到入口文件中】+子组件
2.2 全是同步引入js文件:三个入口文件{每个文件均包括了common和三个子组件}

3: chunks: "all"
3.1 有异步引入的js文件:三个入口文件+common文件+三个子组件【相当于原来的文件分别单独打包】
3.2 全是同步引入js文件:三个入口文件+common文件【包括common本身和引入的子组件】

 

4: 使用 DLLPlugin 提取第三方不变化的代码库:

 
splitchunksPlugin 每次打包的时候还是会去处理一些第三方依赖库,只是它能把第三方库文件和我们的代码分开掉,生成一个独立的js文件。但是它还是不能提高打包的速度。

DLLPlugin 它能把第三方库代码分离开,并且每次文件更改的时候,它只会打包该项目自身的代码。所以打包速度会更快。

DLLPlugin:
vendor.dll.js文件:存放第三方库
venfor-manifest.json文件:包含所有代码库的一个索引

DllReferencePlugin:在webpack.config.js 文件中使用的
作用是用该插件读取 venfor-manifest.json文件 查询第三方库
 
首先编写webpack.dll.js文件:
const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin'); 

module.exports = {
  mode:'production',
  // 入口文件
  entry: {
    // 项目中用到该两个依赖库文件
    vue: ['vue']
  },
  // 输出文件
  output: {
    // 文件名称
    filename: '[name].dll.js', 
    // 将输出的文件放到dist目录下
    path: path.resolve(__dirname, '../static'),

    /*
     存放相关的dll文件的全局变量名称,比如对于jquery来说的话就是 _dll_jquery, 在前面加 _dll
     是为了防止全局变量冲突。
    */
    library: '[name]_library'
  },
  plugins: [
    new CleanWebpackPlugin(), //注意在webpack4 中 不需要定于删除哪个文件夹 默认会根据output中生成的path路径删掉
    // 使用插件 DllPlugin
    new DllPlugin({
      /*
       该插件的name属性值需要和 output.library保存一致,该字段值,也就是输出的 manifest.json文件中name字段的值。
       比如在jquery.manifest文件中有 name: '_dll_jquery'
      */
      name: '[name]_library',
      context:__dirname, //context (可选): manifest文件中请求的上下文,默认为该webpack文件上下文
/* 生成manifest文件输出的位置和文件名称 */
      path: path.join(__dirname, '../static/', '[name].manifest.json')
    })
  ]
};

 

在 webpack.prod.js 文件中调用索引文件
 
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
const proConfig = {
    plugins:[
        new DllReferencePlugin({
            context:__dirname,
            manifest:require('../static/vue.manifest.json')
        })
    ]
}

 

然后增加 package.json 文件:

 "dll": "webpack --config ./build/webpack.dll.js --hide-modules --progress"

 

最后在模版文件中增加引用:

 <script type="text/javascript" src="../static/vue.dll.js"></script>

所以先执行 npm run dll,在执行npm run build

但是这样修该模版文件不太好,所以引入插件:

const AddAssetHtmlPlugin   = require('add-asset-html-webpack-plugin');
plugins:[
    new AddAssetHtmlPlugin({
        filepath: require.resolve('../static/vue.dll.js'),//相当于path.join(__dirname, '../static/vendordev.dll.js')
        includeSourcemap: false
    })
]

 

注意:

1:  html-webpack-include-assets-plugin  和 add-asset-html-webpack-plugin 的区别

两个插件都是把规定的文件插入到html中:

 

主要的不同是 html-webpack-include-assets-plugin  不会copy文件,而 add-asset-html-webpack-plugin 会copy文件,什么意思呢?

使用 add-asset-html-webpack-plugin 后,会把引入的  filepath: require.resolve('../static/vue.dll.js') 自动引入到 dist 文件夹中,而 html-webpack-include-assets-plugin 则不会;

所以在dev环境下,直接把vue.dll.js 引入dist目录下即可,所以使用  add-asset-html-webpack-plugin;而在production环境下,要把 vue.dll.js 放在dist/lib 文件夹下,所以使用CopyWebpackPlugin 复制vue.dll.js文件,配合 html-webpack-include-assets-plugin 插入html中;

所以分为dev和product环境:

const htmlWebpackIncludeAssetsPlugin = require('html-webpack-include-assets-plugin');
const AddAssetHtmlPlugin   = require('add-asset-html-webpack-plugin');
const CopyWebpackPlugin    = require('copy-webpack-plugin');


//dev环境中,该插件会把 vue.dll.js 文件复制到当前路径下
new AddAssetHtmlPlugin({
  filepath: require.resolve('../static/vue.dll.js'),//相当于path.join(__dirname, '../static/vendordev.dll.js')
  includeSourcemap: false,
}),

//production 环境中 
//  1 需要把 dll 文件复制到打包的 dist/lib 文件夹下 -- CopyWebpackPlugin
//  2 html中引入 dist/lib 下的dll 文件 -- htmlWebpackIncludeAssetsPlugin

new htmlWebpackIncludeAssetsPlugin({ //这个插件是把vue.dll.js 插入到 html 中
  assets:['./lib/vue.dll.js'],
  append:false
}),
new CopyWebpackPlugin([  //文件复制到打包的 dist/lib 文件夹下
  { from: path.join(__dirname, "../static/vue.dll.js"), to: path.join(__dirname, "../dist/lib/vue.dll.js") }
]),

注意2:

 这里引入  AddAssetHtmlPlugin 你会发现即使不使用:

new DllReferencePlugin({
    context:__dirname,
    manifest:require('../static/vue.manifest.json')
})

页面也可以使用vue。

那么  DllReferencePlugin  的作用是什么呢?

我们看打包生成的文件:

情况1: 不使用 DllReferencePlugin:

可以看出,index.js 文件中使用的vue居然还是来自 node_modules 说明并没有把vue第三方库剔除;

对比使用DllReferencePlugin :

 

 发现index中已经没有 vue 第三方库,但是页面仍旧能打开,说明使用的是 dll 文件。

再来看 DllReferencePlugin 的作用:

有了映射文件,在webpack打包的时候就可以结合映射文件以及生成的全局变量,来对需要打包的源代码进行分析。
一旦发现你打包的源代码里面用到了映射文件中已有的文件,则直接使用vendors.dll.js 中的内容,而不会去node_modules里引入该模块

 

5. 引入的loader一定要加上 exclude和include 可以大幅降低打包的代码文件大小

 

6.  使用别名优化:

resolve:{
        extensions:['.js','.vue','.json'],
        alias:{
            "@":path.resolve('src')
        }
}

使用extensions省略后缀名字;使用alias给src加上别名,这样可以简化html中的路径,比如:

 pages/index/index.vue 中要使用 assest/imgs/logo.png 的图片:

<img src="../../assest/imgs/logo.png" alt="" class="img-box">

可以简化成:

<img src="@/assest/imgs/logo.png" alt="" class="img-box">

类似的:

//简化前
import Chinese from '../../component/chinese.vue';
//简化后
import Chinese from '@/component/chinese.vue';

但是对于css注意,在引用路径的字符串前面加上 ~ 的符号,如:@import “~Css/…”。webpack 会以~符号作为前缀的路径视为依赖模块去解析

background: url('~@/assest/imgs/logo.png');

 

7 :vue-router的问题

代码:

import VueRouter from 'vue-router'
import routers from './routers'

Vue.use(VueRouter)

const router = new VueRouter({
mode: 'history',
routers
})

原因:
routes:不是routers

解决办法:
routers改为routes即可。

 

8.解决webpack中css独立成文件后图片路径错误的问题

说明:此配置针对webpack4+

 

配置img图片路径问题:

output:{
    filename:'js/[name].[chunkhash].js',
    path:path.resolve(__dirname,'../dist'),
},
//module
{
    test:/\.(png|gif|jpeg|jpg)$/,
    use:[
        {
            loader:'url-loader?cacheDirectory=true',
            options:{
                name:'img/[name].[ext]',
                limit:1024
            }
        }
    ]
},
//plugins
new MiniCssExtractPlugin({
    filename: devMode ? '[name].css' : 'css/[name].[contenthash].css',
    chunkFilename: devMode ? '[name].css' : 'css/[name].[contenthash].css',
})

生成的图片会在css文件中: css/img/logo.png 这样的话 找不到该图片,因为生成的图片路径应该是 img/logo.png

如果修改output的配置中加入一个 publicPath: '../' 配置,则所有的css文件和js的路径都会变化,所以应该只处理css文件,设置css文件中的公共路径:

output:{
    filename:'js/[name].[chunkhash].js',
    path:path.resolve(__dirname,'../dist'),
},
//module
{
    test:/\.scss$/,
    use:[
        {
            loader:MiniCssExtractPlugin.loader,
            options:{
                publicPath:'../'
            }
        },
        {
            loader:'css-loader',
            options:{
                importLoaders:2,
            }
        },
        'postcss-loader',
        'sass-loader'
    ]
},
{
    test:/\.(png|gif|jpeg|jpg)$/,
    use:[
        {
            loader:'url-loader?cacheDirectory=true',
            options:{
                name:'img/[name].[ext]',
                limit:1024
            }
        }
    ]
},
//plugins
new MiniCssExtractPlugin({
    filename: devMode ? '[name].css' : 'css/[name].[contenthash].css',
    chunkFilename: devMode ? '[name].css' : 'css/[name].[contenthash].css',
})

 

9 为了避免不同路由页面间样式冲突,css样式要加 scoped

<style lang="scss" scoped>
否则 两个路由中对同一个div做不同的样式,后面的样式会覆盖掉前一个路由页面的样式。
 
10: 使用 splitchunksPlugin 提取公共组件和代码不生效,要注意入口js是否使用了异步加载
比如路由js:
import Home from "./view/home";
const ProInsight = () => import(/* webpackPrefetch: true */ /* webpackChunkName: 'proInsight' */ "./view/proInsight");
const ProTarget = () => import(/* webpackPrefetch: true */ /* webpackChunkName: 'proTarget' */ "./view/proTarget");

Vue.use(VueRouter);

const routes = [
    { path: "/", name: "home", component: Home },
    { path: "/proInsight", name: "proInsight", component: ProInsight },
    { path: "/proTarget", name: "proTarget", component: ProTarget }
];

中,首页入口 home文件没有使用异步加载,则该组件所有引用的公共代码,都会被打包到home中,而不会提取出来,

所以,home这个组件也要改成异步加载的形式。这样所有的公共代码才能被提取出来。

 
11. 打包时,报一堆警告:
 
 
stats: { 
    warningsFilter: (warning) => /Conflicting order between/gm.test(warning),
    children: false 
}

 

posted @ 2019-07-28 15:23  小猪冒泡  阅读(2736)  评论(0编辑  收藏  举报