基於webpack4 構建多頁(mpa)應用

本教程 github地址 若是對你有幫助歡迎點贊。javascript

雖然當前大前端時代,web應用大多已經作成單頁應用(spa),可是在某些場景下,咱們的項目仍是能夠作成多頁應用(mpa)。在繼續往下看以前,你須要對webpack、nodejs、npm基礎概念有必定的瞭解,若是還不熟悉,請移步各自的官網webpacknodejsnpmcss

本文將從如下幾個方面逐步講解:
html

  • mpa項目目錄結構
  • 最簡單的多頁入口配置
  • 腳本自動生成多頁入口配置。
  • 經常使用loader配置
  • 公共代碼拆分 && 打包構建優化。
  • 利用contentHash作持久化緩存

項目目錄結構

新建一個文件夾,名字隨意,假設命名爲webpack-mpa。而後定位到該目錄,執行命令 npm init, 基本上一路回車就好,按照下面的目錄結構把app裏面的文件夾建好。開發源代碼放於app子目錄,app目錄下的assets爲項目所需資源,pages目錄爲本應用的核心:多入口,configs目錄下面存放webpack配置文件,dist爲打包輸出目錄,scripts爲啓動和打包腳本存放目錄,其餘的就不一一介紹了,前端同窗基本上一看就懂。前端

├── README.md
├── app // 開發源代碼目錄
│   ├── assets // 資源目錄
│   │   ├── images
│   │   └── styles
│   │       ├── common.less
│   └── pages // 多頁面入口目錄
│       ├── entry1
│       │   ├── index.html
│       │   ├── index.js
│       │   ├── index.less
│       └── entry2
│           ├── index.html
│           ├── index.js
│           └── index.less
├── configs // webpack配置文件目錄
│   ├── env.js
│   ├── module.js
│   ├── optimization.js
│   ├── plugins.js
│   ├── webpack.config.babel.js
│   └── webpackDevServer.config.babel.js
├── dist // 打包輸出目錄
├── package.json
├── scripts // 啓動和打包腳本
│   ├── build.js
│   ├── devServer.js
│   └── start.js
├── yarn-error.log
├── yarn.lock
複製代碼

最簡單的多頁入口配置

項目啓動準備

從webpack4開始,配置文件覺得xxx.babel.js結尾的,能夠直接使用es6 module語法,可是須要安裝@babel/register @babel/core @babel/reset-env。另外添加cross-env插件,能夠跨平臺設置環境變量。java

yarn add webpack webpack-cli cross-env @babel/register @babel/core @babel/preset-env --dev
複製代碼

基本依賴添加完畢以後,在根目錄下建立.babelrc文件node

.babelrcjquery

{
  "presets": [
    [
      "@babel/preset-env"
    ]
  ]
}
複製代碼

在configs目錄下新建一個webpack.config.babel.js文件。webpack

webpack.config.babel.jsgit

import path from "path";

const { NODE_ENV } = process.env;
export default {
  target: "web", // 構建的項目運行平臺。有web electron node等可選
  mode: NODE_ENV // webpack運行模式,默認爲production
}
複製代碼

entry

webpack entry有三種寫法:對象、字符串、數組。本示例採用對象寫法。 在webpack.config.babel.js添加入口配置:es6

entry: {
  entry1: path.resolve(__dirname, "../app/pages/entry1/index.js"), // 入口文件1
  entry2: path.resolve(__dirname, "../app/pages/entry2/index.js") // 入口文件2
}
複製代碼

output

多頁應用中,不太可能把全部的入口文件打包到一個輸出文件中,因此咱們須要根據對應的入口文件,單獨打包成對應的輸出文件。

在webpack.config.babel.js添加打包輸出配置:

output: {
  path: path.resolve(__dirname, "../dist"),
  filename: "[name].js"
}
複製代碼

filename中的[name]是一個chunk通配符,chunk能夠簡單的理解爲入口文件名,一個入口文件就是一個chunk。本示例中有兩個chunk:entry1和entry2。 故打包以後輸出的文件爲:entry1.js、entry2.js。

如今咱們的webpack.config.babel.js變成了這樣

import path from "path";

const { NODE_ENV } = process.env;
export default {
  target: "web",
  mode: NODE_ENV,
  entry: {
    entry1: path.resolve(__dirname, "../app/pages/entry1/index.js"),
    entry2: path.resolve(__dirname, "../app/pages/entry2/index.js")
  },
  output: {
    path: path.resolve(__dirname, "../dist"),
    filename: "[name].js"
  }
}
複製代碼

爲了方便,在package.json中添加scripts腳本命令

"build": "cross-env NODE_ENV=production webpack --config configs/webpack.config.babel.js"
複製代碼

此時能夠運行npm run build打包命令,獲得以下相似的輸出結果:

Hash: 29c9e7c031516faa1d3e
Version: webpack 4.40.2
Time: 63ms
Built at: 2019-09-14 20:38:15
    Asset       Size  Chunks             Chunk Names
entry1.js  972 bytes       0  [emitted]  entry1
entry2.js  973 bytes       1  [emitted]  entry2
Entrypoint entry1 = entry1.js
Entrypoint entry2 = entry2.js
[0] ./app/pages/entry1/index.js 57 bytes {0} [built]
[1] ./app/pages/entry2/index.js 56 bytes {1} [built]
複製代碼

能夠看到入口chunk兩個,打包以後輸出名也是入口chunk名。輸出目錄爲根目錄下面的dist。

添加html模板文件

以上的示例僅僅是打包入口js文件,接下來添加html模板,把入口文件注入到對應的html模板文件中,這須要用到 html-webpack-plugin 插件。

先安裝插件

yarn add html-webpack-plugin clean-webpack-plugin progress-bar-webpack-plugin --dev
複製代碼

把全部的配置都寫在webpack.config.babel.js中,會格外龐大,把webpack的plugin module optimization等拆分出來比較容易管理。在configs目錄下面新建文件plugin.js,每次打包時候用clean-webpack-plugin清除dist目錄,progress-bar-webpack-plugin用於顯示構建進度。

plugin.js

import path from "path";

import HtmlWebpackPlugin from "html-webpack-plugin";
import { CleanWebpackPlugin } from "clean-webpack-plugin";
import ProgressBarPlugin from "progress-bar-webpack-plugin";
const plugins = [
  new CleanWebpackPlugin(),
  new ProgressBarPlugin(),
  new HtmlWebpackPlugin({
    template: path.resolve(__dirname, "../app/pages/entry1/index.html"),
    filename: `entry1.html`,
    chunks: ["entry1"]
  }),
  new HtmlWebpackPlugin({
    template: path.resolve(__dirname, "../app/pages/entry2/index.html"),
    filename: `entry2.html`,
    chunks: ["entry2"]
  })
]

export default [...plugins];
複製代碼

ok,最簡單的例子完成了。多頁面應用基本原理就是添加多個入口和打包輸出多個出口。這個示例中,咱們是手動添加entry入口文件和模板的,若是頁面多了以後,webpack配置代碼將變的特別多,並且每次添加新頁面以後就要去改配置,變的十分不方便。下一小節 腳本自動生成多頁入口配置 將解決這個問題。

腳本自動生成多頁入口配置

自動生成多入口配置關鍵在於按照規定的目錄結構動態掃描全部入口文件生成入口配置,同時生成html模板插件配置。因爲屢次用到讀取app目錄,在configs目錄下面新建一個env.js文件,用於存放屢次用到的變量,並預約義一些變量。

env.js

import path from "path";

export const { NODE_ENV, BUNDLE_ANNALYZE } = process.env;
export const isDevelopment = NODE_ENV === "development";
export const isProduction = NODE_ENV === "production";
export const shouldUseSourceMap = true
export const PORT = process.env.port || 9001;
export const PROTOCOL = process.env.HTTPS === 'true' ? 'https' : 'http';
export const HOST = "127.0.0.1";
export const appPath = path.join(process.cwd(), "./app");
export const isBundleAnalyze = BUNDLE_ANNALYZE === "analyze";
複製代碼

自動生成多頁入口配置主邏輯

plugins.js

import path from "path";
import fs from "fs";
import glob from "glob";
import HtmlWebpackPlugin from "html-webpack-plugin";
import { CleanWebpackPlugin } from "clean-webpack-plugin";
import ProgressBarPlugin from "progress-bar-webpack-plugin";

import {
  appPath,
} from "./env";

function getEntry () {
  let entry = {};
  glob.sync(path.resolve(appPath, "pages/**/index.js"))
    .forEach(function (fileDir) {
      let pathObj = path.parse(fileDir);
      // 用文件夾名字做爲入口名。
      let entryName = pathObj.dir.match(/\/\w+$/g)[0].split("/")[1];
      entry[entryName] = fileDir;
    });
  return entry;
};

const entry = getEntry();

const plugins = [
  new CleanWebpackPlugin(),
  new ProgressBarPlugin()
];

function getHtmlWebpackPluginConfigs () {
  const res = [];
  for (let [entryName] of Object.entries(entry)) {
    const htmlFilePath = `${appPath}/pages/${entryName}/index.html`;
    if (!fs.existsSync(htmlFilePath)) {
      throw new Error(`file: ${htmlFilePath} not exist`);
    }
    const plugin = new HtmlWebpackPlugin({
      template: htmlFilePath,
      filename: `${entryName}.html`,
      chunks: [entryName]
    });
    res.push(plugin);
  }
  return res;
}

export { entry };
export default [...plugins, ...getHtmlWebpackPluginConfigs()];
複製代碼

修改以後的webpack.config.babel.js

import path from "path";
import plugins, { entry } from "./plugins";

const { NODE_ENV } = process.env;

export default {
  target: "web",
  mode: NODE_ENV,
  entry,
  output: {
    path: path.resolve(__dirname, "../dist"),
    filename: "[name].js"
  },
  plugins
}
複製代碼

getEntry函數中用glob插件掃描app目錄下的pages目錄下面的index.js,而且把包含indexjs的目錄做爲入口名,獲得一個entry配置,而後getHtmlWebpackPluginConfigs函數遍歷全部的entry配置,生成html模板插件配置。這種掃描規則要求全部的頁面入口都在pages第一級目錄,以文件夾形式存在,文件夾名做爲入口配置名,下面必須包含index.js和index.html爲真正的入口文件和html模板文件。

在pages目錄下面新建文件entry3,裏面新建index.html和index.js,執行npm run build能夠看到dist目錄中已經有了三個html和三個js文件。

dist
├── entry1.html
├── entry1.js
├── entry2.html
├── entry2.js
├── entry3.html
└── entry3.js
複製代碼

固然了,目錄的規則和掃描的規則,按照每一個人和具體項目的狀況,能夠自行定義修改。至此,webpack多頁應用的大致框架已經搭建好了,必定要理解webpack的entry和output思想,而後動態掃描文件做爲入口,重在思想

es2015+ 環境配置

webpack最強大的地方就是各類插件和lodaer了(其實這也是最噁心的地方~~~)。不一樣的人和項目,須要根據實際需求配置,在此列舉一些基礎loader配置。

在configs.js中新建module.js,並寫入一些常見loader基礎配置

module.js

import {
  appPath,
  isDevelopment,
  isProduction,
  shouldUseSourceMap
} from "./env";
import PostCssPresetEnv from "postcss-preset-env";
import PostcssFlexBugsfixes from "postcss-flexbugs-fixes";
import friendlyFormatter from "eslint-formatter-friendly"

const postCssLoaderConfig = {
  loader: "postcss-loader",
  options: {
    ident: 'postcss',
    plugins: () => [
      PostcssFlexBugsfixes,
      PostCssPresetEnv({
        autoprefixer: {
          flexbox: 'no-2009',
          overrideBrowserslist: [
            "last 100 version"
          ]
        },
        stage: 3,
      })
    ],
    sourceMap: isProduction && shouldUseSourceMap,
  },
}
export default {
  rules: [
    { // 這個要先與babel-loader以前定義
      enforce: "pre",
      test: /\.js$/,
      exclude: /node_modules/,
      loader: "eslint-loader",
      options: {
        formatter: friendlyFormatter
      }
    }, {
      test: /\.js$/,
      include: appPath,
      use: "babel-loader"
    }, {
      test: /\.css$/,
      use: [
        isDevelopment && "style-loader",
        "css-loader",
        postCssLoaderConfig
      ].filter(Boolean)
    }, {
      test: /\.less$/,
      include: appPath,
      use: [
        isDevelopment && "style-loader",
        "css-loader",
        "less-loader",
        postCssLoaderConfig
      ].filter(Boolean)
    }, {
      test: /\.(png\jpe?g|gif)$/,
      use: ["file-loader"]
    }, {
      test: /\.(png|jpg|gif)$/,
      use: [{
        loader: "url-loader",
        options: {
          limit: 8 * 1024, // 小於這個時將會已base64位圖片打包處理
          outputPath: "images"
        }
      }]
    }, {
      test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/,
      loader: "url-loader",
      options: {
        limit: 10000
      }
    }, {
      test: /\.html$/,
      use: ["html-withimg-loader"] // html中的img標籤
    }
  ]
}
複製代碼

同時添加.eslintrc.js

module.exports = {
  "env": {
    "browser": true,
    "commonjs": true,
    "es6": true
  },
  "extends": "standard", 
  "parserOptions": {
    "sourceType": "module"
  },
  "rules": {
    "indent": ["error", 2],
    "linebreak-style": ["error", "unix"],
    "quotes": ["error", "double"],
    "semi": ["error", "always"],
    "import/no-duplicates": [0]
  }
};
複製代碼

以及修改.babelrc

{
  "presets": [
    [
      "@babel/preset-env", {
        "corejs": 3,
        "targets": {
          "browsers": "> 0.25%"
        },
        "useBuiltIns": "usage"
      }
    ]
  ],
  "plugins": [
    ["@babel/plugin-transform-runtime"]
  ]
}
複製代碼

loader實在是太多了也太複雜了,須要裝的插件也太多了,能夠參考個人github裏面的package.json,把全部的插件copy過來準沒錯。各類插件具體配置略過,官方教程很是好。

公共代碼拆分 && 打包構建優化

爲了項目方便,如今webpack.config.babel.js添加resolve配置

resolve: {
  extensions: [".js", ".css", ".less", ".json"],
  alias: {
    "@": appPath,
  }
}
複製代碼

如今咱們在entry1和entry2下面的js都加上以下代碼

import _ from "lodash";
import $ from "jquery";
import "reset.css";
import "bootstrap/dist/css/bootstrap.min.css";

console.log(_, $);
async function test () {
  console.log("start");
}
test();
複製代碼

webpack4裏面的opimization配置,顧名思義就是優化的意思,裏面真是大有文章啊,webpack4比起一、二、3真是進步明顯。 新建一個optimization.js

import { isProduction, shouldUseSourceMap } from "./env";
import TerserWebpackPlugin from "terser-webpack-plugin";

export default {
  minimizer: [
    // This is only used in production mode
    isProduction && new TerserWebpackPlugin({
      // Use multi-process parallel running to improve the build speed
      // Default number of concurrent runs: os.cpus().length - 1
      parallel: false,
      // Enable file caching
      cache: false,
      sourceMap: shouldUseSourceMap,
    }),
  ].filter(Boolean)
}
複製代碼

如今的optimization.js還很是簡單,只是加入了一個壓縮插件。從如今開始咱們不只要拆分代碼,每一步優化以後打包都查看打包時間,分秒必爭,作好最大的構建優化。開始第一次打包統計:

Build completed in 6.304s
Hash: 74a31df5d396423a4b19
Version: webpack 4.40.2
複製代碼

特此說明:個人電腦是macos 10.13.6系統,cpu爲intel i7 8700(比白蘋果還好用的黑蘋果),每一個人的電腦cpu,內存、系統等不一樣,結果僅供參考,以本身每次構建優化以後時間減小的百分比爲主就好了。

啓用多核心壓縮js

TerserWebpackPlugin插件中 parallel 參數表示是否開始多線程,默認設置是cpu線程數減1,如今把它設置爲true。繼續打包:

Build completed in 4.185s
Hash: 74a31df5d396423a4b19
Version: webpack 4.40.2
複製代碼

提高可觀啊...

開啓緩存

TerserWebpackPlugin插件中 cache 表示是否開啓緩存,若是下次build過程當中,發現有未更改的chunk,則會直接讀取緩存,減小重複打包節省時間。設置爲true繼續打包:

Build completed in 1.887s
Hash: 74a31df5d396423a4b19
Version: webpack 4.40.2
複製代碼

注意開啓緩存以後,第一次打包仍是和以前的同樣是4.2s左右,由於以前沒有開啓緩存,故沒有緩存能夠讀取,第二次再打包,有緩存能夠讀取,時間大大減小。

代碼拆分

根據以前的打包結果,查看dist目錄獲得以下:

-rw-r--r--  1 lijialin  staff   296B  9 14 23:25 entry1.html
-rw-r--r--  1 lijialin  staff   372K  9 14 23:25 entry1.js
-rw-r--r--  1 lijialin  staff   296B  9 14 23:25 entry2.html
-rw-r--r--  1 lijialin  staff   372K  9 14 23:25 entry2.js
-rw-r--r--  1 lijialin  staff   296B  9 14 23:25 entry3.html
-rw-r--r--  1 lijialin  staff   955B  9 14 23:25 entry3.js
複製代碼

仔細觀察發現entry1.js和entry2.js的js文件很是大,兩個都包含了jquery和lodash,以及reset.css和bootstrap.css。如今須要作兩件事兒:

  • 抽離公共代碼
  • css分離打包

webpack4以前用CommonsChunkPlugin抽取公衆代碼,該插件已經從webpack4中移除,新版本用SplitChunksPlugin作拆分,這是一個webpack自帶的優化選項配置optimization下面的一個屬性。

optimization.js

import { isProduction, shouldUseSourceMap } from "./env";
import TerserWebpackPlugin from "terser-webpack-plugin";
import OptimizeCSSAssetsPlugin from "optimize-css-assets-webpack-plugin";

export default {
  splitChunks: {
    cacheGroups: {
      vendor: { // 抽離第三方插件
        test: /node_modules/, // 指定是node_modules下的第三方包
        chunks: "initial",
        name: "vendor", // 打包後的文件名,任意命名
        priority: 10, // 設置優先級,防止和自定義的公共代碼提取時被覆蓋,不進行打包
      },
      common: { // 抽離本身寫的公共代碼,common這個名字能夠隨意起
        chunks: "all",
        name: "common", // 任意命名
        minSize: 0, // 只要大小超出設置的這個數值,就生成一個新包
        minChunks: 2,
        priority: 9
      }
    }
  },
  minimizer: [
    // This is only used in production mode
    isProduction && new TerserWebpackPlugin({
      // Use multi-process parallel running to improve the build speed
      // Default number of concurrent runs: os.cpus().length - 1
      parallel: true,
      // Enable file caching
      cache: true,
      sourceMap: shouldUseSourceMap,
    }),
    // This is only used in production mode
    isProduction && new OptimizeCSSAssetsPlugin({
      cssProcessorOptions: {
        map: shouldUseSourceMap
          ? {
              inline: false,
              // `annotation: true` appends the sourceMappingURL to the end of
              // the css file, helping the browser find the sourcemap
              annotation: true,
            }
          : false,
      },
    }),
  ].filter(Boolean)
}
複製代碼

module.js中在css和less的loader裏面修改如下配置,修改以後部分代碼以下:

{
  test: /\.css$/,
  use: [
    isDevelopment && "style-loader",
    isProduction && {
      loader: MiniCssExtractPlugin.loader,
      options: {
        publicPath: "../"
      }
    },
    "css-loader",
    postCssLoaderConfig
  ].filter(Boolean)
}, {
  test: /\.less$/,
  include: appPath,
  use: [
    isDevelopment && "style-loader",
    isProduction && {
      loader: MiniCssExtractPlugin.loader,
      options: {
        publicPath: "../"
      }
    },
    "css-loader",
    "less-loader",
    postCssLoaderConfig
  ].filter(Boolean)
}
複製代碼

同時修改plugins,添加MiniCssExtractPlugin:

const plugins = [
  new CleanWebpackPlugin(),
  new ProgressBarPlugin(),
  isProduction && new MiniCssExtractPlugin({
    filename: 'css/[name].css',
    chunkFilename: 'css/[id].css'
  }),
].filter(Boolean);
複製代碼

在這兒咱們作了兩件重要的事兒,第一就是拆分代碼,把公共的css js拆分到一個文件中,第二就是在拆分的公共文件中提取公共css。 有關webpack4 splitChunks的具體規則,官網介紹的比較好。

在app的assets目錄下面新建一個common.less,隨便寫點代碼,而後entry1和entry2同時引入這個less文件

再次打包,打包以後dist目錄長這樣:

-rw-r--r--  1 lijialin  staff   111B  9 15 00:42 0.css
-rw-r--r--  1 lijialin  staff   388B  9 15 00:42 0.css.map
-rw-r--r--  1 lijialin  staff   206K  9 15 00:42 1.css
-rw-r--r--  1 lijialin  staff   286K  9 15 00:42 1.css.map
-rw-r--r--  1 lijialin  staff    80B  9 15 00:42 common.js
-rw-r--r--  1 lijialin  staff   296B  9 15 00:42 entry1.html
-rw-r--r--  1 lijialin  staff   1.9K  9 15 00:42 entry1.js
-rw-r--r--  1 lijialin  staff   296B  9 15 00:42 entry2.html
-rw-r--r--  1 lijialin  staff   1.9K  9 15 00:42 entry2.js
-rw-r--r--  1 lijialin  staff   296B  9 15 00:42 entry3.html
-rw-r--r--  1 lijialin  staff   955B  9 15 00:42 entry3.js
-rw-r--r--  1 lijialin  staff   160K  9 15 00:42 vendor.js
複製代碼

公共的js和css已經被單獨拆分出來了。0.css是咱們寫的common.less裏面的內容,雖然很小,可是也被拆分出來了,是經過splitchunks裏面的minSize控制的。1.css是reset.css和bootstrap.css的合併,由於都在node_modules中,因此被打包成了一個文件。vender.js則包含了jquery和lodash,以及babel runtime轉化的部分代碼。提醒:webpack4的splitchunk是個很重要的東西,必定要去好好的學習

修改配置文件以後連續打包兩次,第二次能夠讀取緩存,打包信息:

Build completed in 2.86s
Hash: 2449ed9d5b9e289f3001
Version: webpack 4.40.2
複製代碼

時間變長了,拆分代碼,必然的會有額外的開銷。若是咱們把TerserWebpackPlugin中的cache和parallel都設置爲false,再試試?

Build completed in 5.31s
Hash: 2449ed9d5b9e289f3001
Version: webpack 4.40.2
複製代碼

可見開啓緩存和多核心提高很是大的,畢竟實際業務中,大多時候變的是業務代碼,少數時候變的是本身的公共代碼,極少時候三方庫纔會變化好比版本更新。

抽離三方庫爲cdn

咱們以bootcdn爲三方cdn例子介紹。 分別在entry1和entry2的index.html中添加

<link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
  <script src="https://cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>
複製代碼

index.js中取消對bootstrap.css的引用,webpack.config.babael.js添加external外部依賴

externals: {
  jquery: "jQuery"
}
複製代碼

修改配置文件以後連續打包兩次,第二次能夠讀取緩存,打包信息:

Build completed in 2.302s
Hash: 15ae6682578c7c9e04e8
Version: webpack 4.40.2
複製代碼

嗯,減小了0.5s,仍是不錯了,越日後越難優化了... 查看如下dist目錄如今長啥樣兒:

-rw-r--r--  1 lijialin  staff   111B  9 15 01:00 0.css
-rw-r--r--  1 lijialin  staff   388B  9 15 01:00 0.css.map
-rw-r--r--  1 lijialin  staff   807B  9 15 01:00 1.css
-rw-r--r--  1 lijialin  staff   1.5K  9 15 01:00 1.css.map
-rw-r--r--  1 lijialin  staff    80B  9 15 01:00 common.js
-rw-r--r--  1 lijialin  staff   472B  9 15 01:00 entry1.html
-rw-r--r--  1 lijialin  staff   1.9K  9 15 01:00 entry1.js
-rw-r--r--  1 lijialin  staff   472B  9 15 01:00 entry2.html
-rw-r--r--  1 lijialin  staff   1.9K  9 15 01:00 entry2.js
-rw-r--r--  1 lijialin  staff   296B  9 15 01:00 entry3.html
-rw-r--r--  1 lijialin  staff   955B  9 15 01:00 entry3.js
-rw-r--r--  1 lijialin  staff    76K  9 15 01:00 vendor.js
複製代碼

jquery被抽離出去用cdn加載後,vender體積大量減小,因爲咱們的業務文件幾乎沒啥代碼,@babel/tranform-runtime插件所需的代碼必然也不多,故能夠判定基本都是lodash的代碼。實際開發中,也不可能引用整個lodash,好比咱們用到clonedeep,那麼就單獨引用它。lodash提供了一個es版本,終於能夠按需加載了。

import { cloneDeep } from "lodash-es";
...
console.log(cloneDeep, $);
...
test();
複製代碼

改一下entry1和entry2中的代碼,再次打包

Build completed in 1.718s
Hash: ece442821ba575310bbd
Version: webpack 4.40.2
複製代碼

dist文件

-rw-r--r--  1 lijialin  staff   111B  9 15 01:13 0.css
-rw-r--r--  1 lijialin  staff   388B  9 15 01:13 0.css.map
-rw-r--r--  1 lijialin  staff   807B  9 15 01:13 1.css
-rw-r--r--  1 lijialin  staff   1.5K  9 15 01:13 1.css.map
-rw-r--r--  1 lijialin  staff    81B  9 15 01:13 common.js
-rw-r--r--  1 lijialin  staff   472B  9 15 01:13 entry1.html
-rw-r--r--  1 lijialin  staff   1.9K  9 15 01:13 entry1.js
-rw-r--r--  1 lijialin  staff   472B  9 15 01:13 entry2.html
-rw-r--r--  1 lijialin  staff   1.9K  9 15 01:13 entry2.js
-rw-r--r--  1 lijialin  staff   296B  9 15 01:13 entry3.html
-rw-r--r--  1 lijialin  staff   955B  9 15 01:13 entry3.js
-rw-r--r--  1 lijialin  staff    20K  9 15 01:13 vendor.js
複製代碼

時間再次減小,而且vender.js公共依賴體積大大減小。至此,一個引用jquery lodash bootstrap的多頁面demo打包構建優化到1.7s左右,具體每一個人電腦配置不一樣所需時間不一樣,就算後面再加上一些組件庫,寫n多業務代碼(只要不瞎寫),只要合理的運用三方cdn,抽離node_modules公共資源,創建緩存,我估計應該也不會超過10s的構建時間,好,就算10s不夠,翻個倍20s吧,也不算很長,比起一些瞎搞的項目動則打包以分鐘爲計,也是挺不錯的...

持久化緩存

以上的每次打包輸出都是直接用的entry名字,沒有帶版本號,實際生產中確定是行不通的。

咱們把webpack.config.babel.js中的output輸出稍微改一下:

...
  output: {
    filename: isDevelopment ? "js/[name].bundle.js" : "js/[name].[contentHash:8].js",
    path: isProduction ? path.resolve(__dirname, "../dist") : undefined
  }
...
複製代碼

抽離輸出的css插件也改如下: plugins.js部分代碼

...
const plugins = [
  new CleanWebpackPlugin(),
  new ProgressBarPlugin(),
  isDevelopment && new webpack.HotModuleReplacementPlugin(),
  isProduction && new MiniCssExtractPlugin({
    filename: 'css/[name].[contentHash:8].css',
    chunkFilename: 'css/[id].[contentHash:8].css'
  })
].filter(Boolean);
...
複製代碼

contentHash是之內容生成的,有點相似文件的md5,只要內容不變,hash就不會變,也就是內容沒變的狀況下,輸出的文件名是同樣的,這樣有利於咱們項目作持久化緩存。 最後別忘了在HtmlWebpackPlugin中把splitChunks拆分的chunks加上,也就是common和vender。

plugins.js部分代碼

...
function getHtmlWebpackPluginConfigs () {
  const res = [];
  for (let [entryName] of Object.entries(entry)) {
    const htmlFilePath = `${appPath}/pages/${entryName}/index.html`;
    if (!fs.existsSync(htmlFilePath)) {
      throw new Error(`file: ${htmlFilePath} not exist`);
    }
    const plugin = new HtmlWebpackPlugin({
      template: htmlFilePath,
      filename: `${entryName}.html`,
      chunks: ["vendor", "common", entryName]
    });
    res.push(plugin);
  }
  return res;
}
...
複製代碼

完結

文筆很差,敬請諒解。本教程重在講解思路,歡迎指出錯誤之處和給出寶貴意見。

本教程 github地址 若是對你有幫助歡迎點贊。

相關文章
相關標籤/搜索