記一次webpack優化 create-react-app 從單個文件變為多個文件打包將公共依賴,公共代碼整理到特定js文件里面


版本:

  "webpack": "3.5.1",

  "react": "^15.6.2",

  "antd": "^3.26.7",
1.項目本來是一直跑在內網的,原先項目build之后是在一個js包里面,導致包基本上都在3M+  甚至到5M+,本來在內網上,倒也速度到也不太慢,不過最近有考慮可能要放到外網上展示,所以要搞一下js大小,不然加載慢的一批
2.核心要求:js文件要盡量小,加載速度要快 優先於 打包速度 ,最好能打包速度快的同時 能js文件能盡量小
3.開始
  1).不作任何改變的話,文件大小5M,時間110s
  2).先考慮將單個文件拆分:從router入手,將文件變為懶加載或者說按需加載
   
//router.js
import {asyncComponent} from '@utils/loadable';
const Login = asyncComponent(()=> import(/* webpackChunkName:'Login' */"../pages/Login/index"))//中間的注釋可以讓webpack按照這個名字打包 便於分辨
//loadable.js

import React from 'react'
import Loadable from 'react-loadable'
import { Button, Result, Spin } from 'antd'
import store from '@store/store'

const extra = ({ retry }) => (
  <Button type='primary' onClick={retry}>重新加載</Button>
)

const ErrorComponent = props => (
    <Result
        status='500'
        title='504'
        subTitle='似乎出了點問題'
        extra={extra(props)}
    />
)

const LoadingComponent = (
    <div style={{ textAlign: 'center', marginTop: 100 }}>
        <Spin size='large'/>
    </div>
)

const Loading = props => {
    if (props.error || props.timedOut) {
            return <ErrorComponent {...props}/>
        } else {
            return LoadingComponent
    }
}

const DefaultProps = {
    loading: Loading,
    timeout: 20000, // 20 seconds
    delay: 300 // 0.3 seconds
}

/**
 * 異步加載頁面
 * @param component
 * @param callback
 * @returns {*}
 */
export function asyncComponent (component, callback) {
    return Loadable({
        ...DefaultProps,
        loader: component,

        render (loaded, props) {
            callback && callback()
            const Component = loaded.default
            props = {...props, ...store.getState().Task}
            return <Component {...props}/>
        }
    })
}

/**
 * 異步加載頁面 同時 添加其他文件
 * @param component
 * @param models
 * @returns {*}
 */
export function asyncComponentWithModels (component, models) {
    const name = 'componentName'
    return Loadable.Map({
        ...DefaultProps,
        loader: {
            [name]: component,
            ...models
        },
        render (loaded, props) {
            const Component = loaded[name].default
            return <Component {...props}/>
        }
    })
}

這樣,開始直接打包,然后發現最新打包時間需要240s左右,而且每一個包都很大少則幾百K,多則1,2M,,,繼續,然后發現里面很多重復的包比如echarts,moment等等(可以用webpack-bundle-analyzer和speed-measure-webpack-plugin分析你的包,百度很簡單)

按照這個思路,有兩個方法dll和CommonsChunkPlugin兩種

  ⑴dll

  之所以需要這個js文件是為了解決antd的按需加載,不過結果不盡人意

//package.json新增命令
"build:dll": "cross-env NODE_ENV=production node scripts/dll.js",//如果提示cross-env不是一個命令的話  npm i cross-env 就可以了
//根目錄script新增dll.js
const webpack = require('webpack')
const chalk = require('chalk');
const { generator } = require('../config/webpack.dll.config')


// const compilerAntd = webpack(reactConfig);

new Promise((resolve, reject) => {
    const reactConfig = generator('react',[
        'react', 'react-dom', 'react-redux', 'redux', 'react-router', 'react-loadable',
        'echarts', 'moment', 'jquery', 'html2canvas', 'jspdf'
    ], false)
    const compilerReact = webpack(reactConfig);
    compilerReact.run((err, stats) => {
        if(err){
            console.log(err)
        }
        resolve(stats)
    })
}).then((stats) => {
    const antdConfig = generator('antdVendor', [
        'antd/lib/button',
        'antd/lib/modal',
        'antd/lib/page-header',
        'antd/lib/table',
        'antd/lib/card',
        'antd/lib/form',
        'antd/lib/input',
        'antd/lib/icon',
        'antd/lib/message',
        'antd/lib/select',
        'antd/lib/descriptions',
        'antd/lib/input-number',
        'antd/lib/tag',
        'antd/lib/date-picker',
        'antd/lib/inputNumber',
        'antd/lib/dropdown',
        'antd/lib/progress',
        'antd/lib/spin',
        'antd/lib/checkbox',
        'antd/lib/col',
        'antd/lib/row',
        'antd/lib/config-provider',
        'antd/lib/layout',
        'antd/lib/menu',
        'antd/lib/radio',
        'antd/lib/switch',
        'antd/lib/tree',
        'antd/lib/upload',
        'antd/lib/typography',

    ], true)
    const compilerAntd = webpack(antdConfig);
    compilerAntd.run((err, stats) => {
        if(err){
            console.log(err)
        }else{
            console.log(chalk.green(`success!!!`));
        }
    })
})
//config文件新增webpack.dll.config.js
// webpack.dll.config.js
const Webpack = require('webpack')
const fs = require('fs-extra');
const path = require('path');
const paths = require('./paths');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WebpackBar = require('webpackbar');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

function generator(name, arr, needDepend){
    !needDepend && fs.emptyDirSync(paths.vendorSrc);
    return {
        devtool: false,
        entry: {
            [name]: arr,
        },
        output: {
            filename: "[name].dll.[hash].js",
            path: paths.vendorSrc,
            libraryTarget: 'var',
            library: '_dll_[name]_[hash]'
        },
        plugins: [
            new WebpackBar(),
            new Webpack.DefinePlugin({
                "process.env": { 
                   NODE_ENV: JSON.stringify("production") 
                 }
            }),
            new Webpack.DllPlugin({
                path: paths.vendorSrc + '/[name].manifest.json',
                name: '_dll_[name]_[hash]'
            }),
            new HtmlWebpackPlugin({
                filename: paths.appHtml,
                template: needDepend ? paths.appHtml : paths.appHtmlTemplate,
                inject: true,
                minify: {
                    removeComments: true,
                    collapseWhitespace: true,
                    removeRedundantAttributes: true,
                    useShortDoctype: true,
                    removeEmptyAttributes: true,
                    removeStyleLinkTypeAttributes: true,
                    keepClosingSlash: true,
                    minifyJS: true,
                    minifyCSS: true,
                    minifyURLs: true,
                }
            }),
            new Webpack.optimize.UglifyJsPlugin({
                compress: {
                  warnings: false,
                }
            }),
            needDepend && new Webpack.DllReferencePlugin({
                manifest: require(path.join(__dirname, '../public/vendor/react.manifest.json'))
            }),
            new BundleAnalyzerPlugin({
                //  可以是`server`,`static`或`disabled`。
                //  在`server`模式下,分析器將啟動HTTP服務器來顯示軟件包報告。
                //  在“靜態”模式下,會生成帶有報告的單個HTML文件。
                //  在`disabled`模式下,你可以使用這個插件來將`generateStatsFile`設置為`true`來生成Webpack Stats JSON文件。
                analyzerMode: 'server',
                //  將在“服務器”模式下使用的主機啟動HTTP服務器。
                analyzerHost: '127.0.0.1',
                //  將在“服務器”模式下使用的端口啟動HTTP服務器。
                analyzerPort: name == "react" ? 8887 : 8886, 
                //  路徑捆綁,將在`static`模式下生成的報告文件。
                //  相對於捆綁輸出目錄。
                reportFilename: 'report.html',
                //  模塊大小默認顯示在報告中。
                //  應該是`stat`,`parsed`或者`gzip`中的一個。
                //  有關更多信息,請參見“定義”一節。
                defaultSizes: 'parsed',
                //  在默認瀏覽器中自動打開報告
                openAnalyzer: true,
                //  如果為true,則Webpack Stats JSON文件將在bundle輸出目錄中生成
                generateStatsFile: false, 
                //  如果`generateStatsFile`為`true`,將會生成Webpack Stats JSON文件的名字。
                //  相對於捆綁輸出目錄。
                statsFilename: 'stats.json',
                //  stats.toJson()方法的選項。
                //  例如,您可以使用`source:false`選項排除統計文件中模塊的來源。
                //  在這里查看更多選項:https:  //github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21
                statsOptions: null,
                logLevel: 'info' //日志級別。可以是'信息','警告','錯誤'或'沉默'。
              })
        ].filter(Boolean),
    }
}

module.exports = {
    generator
}
//修改paths.js  在最后面新增
vendorSrc: resolveApp('public/vendor'),
appHtmlTemplate: resolveApp('public/indexTemplate.html'),
//在webpack.config.prod.js 修改或者webpack.config.js修改
module.exports = {
   plugins: [
       new webpack.DllReferencePlugin({
            manifest: require(path.join(__dirname, '../public/vendor/react.manifest.json'))
      })
    ] 
}    

這樣運行npm run build:dll 應該會成功打出兩個js和兩個json並且都掛在html里面了(省的自己手動寫js文件路徑了)

  ⑵CommonsChunkPlugin

//在webpack.config.prod.js 修改或者webpack.config.js修改
module.exports = {
   plugins: [
       new webpack.optimize.CommonsChunkPlugin({
         name: 'vender',
         minChunks: function (module, count) {
           return (
              module.resource &&
              /\.js$/.test(module.resource) &&
              module.resource.indexOf(
                path.join(__dirname, '../node_modules')
            ) === 0
          )
       }
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'components',
      minChunks: function (module, count) {
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../src')
          ) === 0
        )
      }
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: "manifest",
      chunks: ['components', "vender"]
    })
} 

經過這一些系列的改動(antd的按需加載還是沒有完全搞定,還是會有重復打包的地方),現在基本是除了dll包是在1M+,別的都是在500kb一下了,也可以講究一下了

還有一個優化的地方就是使用多線程

const HappyPack = require('happypack');
  {
     test: /\.(js|jsx)$/,
     include: paths.appSrc,
     use: ['happypack/loader?id=babel'],//在這里修改使用happypack   id是跟后面的值對照
  }
 //plugin
  //開啟多線程
    new HappyPack({
      // 用唯一的標識符 id 來代表當前的 HappyPack 是用來處理一類特定的文件
      id: 'babel',
      // 如何處理 .js 文件,用法和 Loader 配置中一樣
      // 注意:loaders 是 use 的別名
      use: [ 'cache-loader', {
        loader: 'babel-loader',
        options: {
          plugins: [
            ['import', [{ libraryName: "antd", style: 'less' }]],
          ],
          cacheDirectory: true,
        }
      }],
      threads: 10
      // ... 其它配置項
    })


也能減少一點構建時間

最后構建時間壓縮到80s左右,開啟gzip情況下單個js文件都在200KB一下,現在的問題基本上都是在antd的按需加載上,未完待續。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM