zoukankan      html  css  js  c++  java
  • 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 
    }
  • 相关阅读:
    Memento模式
    CSS实现半透明div层的方法
    JS解析json数据(如何将json字符串转化为数组)
    并发容器Map之二:ConcurrentNavigableMap
    JSinArray检查数组中是否存在某个值
    Servlet3.0之七:@WebFilter申明该类为过滤器
    Spring源码阅览——BeanFactory体系结构
    使用 Selenium RC 测试 web 应用程序
    函数式编程
    9 个 Java 处理 Exception 的最佳实践
  • 原文地址:https://www.cnblogs.com/xiaozhumaopao/p/11259130.html
Copyright © 2011-2022 走看看