前端工程化(三)

webpack 打包

模塊化開發(fā)為我們解決了很多問題刮吧,使得代碼組織管理非常的方便捐川,但是又帶來了新的問題友鼻,ES Module 存在環(huán)境兼容問題酗昼,劃分的文件太多廊谓,就會(huì)導(dǎo)致網(wǎng)絡(luò)請(qǐng)求頻繁,不能保證所有資源的模塊化

如果能我們享受模塊化帶來的開發(fā)優(yōu)勢(shì)麻削,又能不必?fù)?dān)心生產(chǎn)環(huán)境的存在這些問題蒸痹,于是就有了 webpack, rollup, Parcel 等工具
webpack 模塊化不等于 js ES modele 模塊,相對(duì)來講是前端的模塊化處理方案呛哟,更加宏觀

  • 快速上手
$ yarn init --yes
$ yarn add webpack webpack-cli -D
$ yarn webpack --version
$ yarn webpack // 默認(rèn)打包src/index.js // 最終存放到dist/main.js
  • webpack 配置文件

在項(xiàng)目根目錄添加 webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/main.js', // 入口文件
  output: {
    filename: 'bundle.js', // 輸出文件名
    path: path.join(__dirname, 'output'), // 輸出文件路徑(絕對(duì)路徑)
  },
};
  • 工作模式

webpack4 新增了工作模式的用法叠荠,大大簡化了配置的復(fù)雜程度;三種工作模式 mode: production development none

$ webpack --mode none
$ webpack --mode production // 默認(rèn)模式
$ webpack --mode development

或者采用配置的方式

const path = require('path');

module.exports = {
  // 這個(gè)屬性有三種取值,分別是 production扫责、development 和 none榛鼎。
  // 1. 生產(chǎn)模式下,Webpack 會(huì)自動(dòng)優(yōu)化打包結(jié)果鳖孤;
  // 2. 開發(fā)模式下者娱,Webpack 會(huì)自動(dòng)優(yōu)化打包速度,添加一些調(diào)試過程中的輔助淌铐;
  // 3. None 模式下肺然,Webpack 就是運(yùn)行最原始的打包,不做任何額外處理腿准;
  mode: 'development',
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist'),
  },
};
  • 資源模塊加載

    webpack 內(nèi)部的 loader 只能處理 js 文件,其他文件我們需要配置對(duì)應(yīng)的 loader 才可以完成打包拾碌,否則會(huì)報(bào)錯(cuò)吐葱。

const path = require('path');

module.exports = {
  mode: 'none',
  entry: './src/main.css',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /.css$/,
        // css-loader作用就是將css代碼轉(zhuǎn)化為js模塊
        // style-loader作用就是將cssloader轉(zhuǎn)化的結(jié)果追加到頁面
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
};
  • 導(dǎo)入資源模塊
    入口文件為 js 文件,根據(jù)代碼的需要?jiǎng)討B(tài)導(dǎo)入其他資源,由 javascript 驅(qū)動(dòng)整個(gè)前端應(yīng)用
const path = require('path');

module.exports = {
  mode: 'none',
  entry: './src/main.css',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /.css$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
};

// main.js
import './main.css';
  • 文件資源加載器

    • file-loader
      經(jīng)過 file-loader 處理后校翔,將文件資源放到我們打包目錄的根目錄弟跑。返回文件資源的訪問路徑,通過 import 就可以拿到文件資源的路徑防症。webpack 默認(rèn)認(rèn)為文件資源放在網(wǎng)站的根目錄下
      會(huì)發(fā)起文件請(qǐng)求
const path = require('path');

module.exports = {
  mode: 'none',
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist'),
    publicPath: 'dist/',
  },
  module: {
    rules: [
      {
        test: /.css$/,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /.png$/,
        use: 'file-loader',
      },
    ],
  },
};
// main.js
import createHeading from './heading.js';
import './main.css';
import iconURL from './icon.png';
// 經(jīng)過file-loader處理后孟辑,將圖片放到我們打包目錄的根目錄。返回圖片的訪問路徑蔫敲,通過import就可以拿到圖片的路徑饲嗽。webpack默認(rèn)認(rèn)為圖片放在網(wǎng)站的根目錄下
const heading = createHeading();

document.body.append(heading);

const img = new Image();
img.src = iconURL;

document.body.append(img);
  • url-loader
    將文件資源轉(zhuǎn)化為 Data Url, 最終返回這個(gè) Data Url奈嘿,不單獨(dú)生成資源文件貌虾,直接嵌入到 bundle.js 中
    當(dāng)資源文件過大時(shí),導(dǎo)致 base64 邊長裙犹,打包的 bundle.js 體積過大

    • Data URLs
      直接表示文件內(nèi)容,使用這種 Url 不會(huì)發(fā)起 Http 請(qǐng)求

      data:[<mediatype>][;base64],<data>
      
      // 協(xié)議 + 媒體類型以及編碼+ 文件內(nèi)容(圖片會(huì)被轉(zhuǎn)化為base64)
      

最佳實(shí)踐:小文件使用 Data URLs, 減少請(qǐng)求次數(shù)尽狠。大文件單獨(dú)提取衔憨,避免 bundle.js 過大,加載時(shí)間過長

module: {
  rules: [
    {
      test: /.css$/,
      use: ['style-loader', 'css-loader'],
    },
    {
      test: /.png$/,
      use: {
        // 必須同時(shí)安裝file-loader,當(dāng)超過limit設(shè)置的值袄膏,url-loader會(huì)自動(dòng)讓file-loader處理
        loader: 'url-loader',
        options: {
          limit: 10 * 1024, // 10 KB
        },
      },
    },
  ];
}
  • 常用 loader 分類

    • 編譯轉(zhuǎn)化類型

    • 文件操作類型

    • 代碼質(zhì)量檢查

  • 處理 ES6+新特性

    webpack 只是打包工具 默認(rèn)處理代碼中的 export 和 import,但對(duì)其他 ES6+新特性不做處理践图,需要 babel-loader

$ yarn add babel-loader @babel/core @babel/preset-env -D
// babel 只是一個(gè)js的轉(zhuǎn)換平臺(tái)〕凉荩基于平臺(tái)通過不同的插件實(shí)現(xiàn)轉(zhuǎn)化

{
  "test": /.js$/,
  "use": {
    "loader": "babel-loader",
    "options": {
      "presets": ["@babel/preset-env"]
    }
  }
}
  • 模塊加載方式
    webpack 兼容多種標(biāo)準(zhǔn)的模塊加載方式

    • ES Module
    • CommonJs
    • AMD
    • import('XXX.css')
    • @import ()
    • @import url()
    • html 中的 img 的 src 屬性
    • background 屬性的 url
    • a 標(biāo)簽的 herf 屬性
      ...
  • webpack 核心工作原理

    • 首先設(shè)置入口文件平项,webpack 會(huì)根據(jù)配置找到入口文件(如果不設(shè)置默認(rèn) src 下面的 index.js 文件)作為我們的打包入口

    • 根據(jù)代碼中出現(xiàn)的 import 或者 require 解析推斷出這個(gè)文件所依賴的其他資源模塊

    • 然后分別延伸解析每一個(gè)資源模塊對(duì)應(yīng)的依賴,形成一個(gè)整個(gè)項(xiàng)目當(dāng)中所有用的資源文件的依賴樹

    • 然后遞歸這個(gè)依賴樹悍及,找到每個(gè)節(jié)點(diǎn)對(duì)應(yīng)的資源文件闽瓢,根據(jù)配置文件的 rules 屬性找到當(dāng)前模塊的加載器(loader),然后交給加載器加載這個(gè)模塊

    • 最終將執(zhí)行完成的結(jié)果放到 output 對(duì)應(yīng)的 bundle.js 中

    • 在整個(gè)過程中,會(huì)通過 webpack 提供的鉤子函數(shù)(生命周期函數(shù))加載對(duì)應(yīng)的任務(wù)心赶。這個(gè)任務(wù)我們也成 plugins

  • webpack 開發(fā)一個(gè) loader
    原則: 對(duì)同一文件所用到的 loader 執(zhí)行完成后, 最終必須返回 javascropt 代碼扣讼,也就是處理當(dāng)前資源的最后的 loader 必須是返回 javascript 代碼

const path = require('path');

module.exports = {
  mode: 'none',
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist'),
    publicPath: 'dist/',
  },
  module: {
    rules: [
      {
        test: /.md$/,
        // 將 md 轉(zhuǎn)化為 html
        use: ['html-loader', './markdown-loader'],
      },
    ],
  },
};
// main.js
import about from './about.md';

console.log(about);
// markdown-loader.js
const marked = require('marked');

module.exports = source => {
  // source為加載進(jìn)來的資源內(nèi)容

  const html = marked(source);
  // 如果不交給下個(gè)loader處理

  // return `module.exports = "${html}"`
  // return `export default ${JSON.stringify(html)}`
  // 如果交給下個(gè)loader處理
  // 返回 html 字符串交給下一個(gè) loader 處理
  return html;
};
  • 常用插件 Plugin

    clean-webpack-plugin
    每次打包前先清除 webpack 輸出目錄

    HtmlWebpackPlugin
    每次打包的文件自動(dòng)生成 html 文件,自動(dòng)引入打包結(jié)果

plugins: [
  new webpack.ProgressPlugin(),
  new CleanWebpackPlugin(),
  // 不額外添加模板的使用
  new HtmlWebpackPlugin({
    title: 'glh', // 設(shè)置標(biāo)題
    meta: {
      // 設(shè)置meta標(biāo)簽
      viewport: 'width=device-width',
    },
    // ...
  }),
];
// 添加模板,讓HtmlWebpackPlugin根據(jù)模板生成
new HtmlWebpackPlugin({
  title: 'glh', // 設(shè)置標(biāo)題
  meta: {
    // 設(shè)置meta標(biāo)簽
    viewport: 'width=device-width',
  },
  template: './public/index.html',
  templateParameters: {
    // 自定義變量
    BASE_URL: './',
  },
  // ...
});
<!--  public/index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>

  <body>
    <noscript>
      <strong
        >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
        properly without JavaScript enabled. Please enable it to
        continue.</strong
      >
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
// 用于生成index.html
new HtmlWebpackPlugin({
  template: './public/index.html',
  // ...
});
// 用于生成about.html
new HtmlWebpackPlugin({
  filename: 'about.html',
  // ...
});

copy-webpack-plugin
對(duì)一些公共資源文件直接復(fù)制到打包目錄中缨叫。比如 public/favicon.ico

new CopyWebpackPlugin({
  patterns: [{ from: 'public/favicon.ico', to: '.' }],
});

我們一般在使用插件的時(shí)候掌握一些經(jīng)常用的就可以椭符。后面根據(jù)需求再去提煉關(guān)鍵詞,搜索自己想用的插件耻姥,當(dāng)然也可以自己寫销钝。插件的約定名稱一般都是 XXX-webpack-plugin,比如我們想要壓縮圖片就可以找 imagemin-webpack-plugin

  • 實(shí)現(xiàn)一個(gè)自定義 plugin

首先要明白:

  • Plugin 其實(shí)就是通過在生命周期的鉤子中掛載函數(shù)實(shí)現(xiàn)擴(kuò)展。類似于我們 React 中的聲明周期琐簇。
    webpack 在工作的過程中給每一個(gè)環(huán)節(jié)都埋下了鉤子蒸健,我們只需要在對(duì)應(yīng)的鉤子下掛載任務(wù)就可以輕松的擴(kuò)展 webpack 的能力

自定義的 Plugin 其實(shí)就是一個(gè)函數(shù),或者包含 apply 的方法的對(duì)象
apply 方法接受一個(gè) compiler 對(duì)象參數(shù)婉商,這個(gè)參數(shù)包含我們整個(gè)構(gòu)建過程中的所有配置信息似忧,通過這個(gè)對(duì)象我們可以注冊(cè)鉤子函數(shù),通過 tap 方法注冊(cè)任務(wù)
tap 方法又接受兩個(gè)參數(shù)丈秩,一個(gè)是插件名稱盯捌,一個(gè)是當(dāng)前次打包執(zhí)行的上下文

  • 和 loader 區(qū)別:loader 是專注實(shí)現(xiàn)資源模塊加載轉(zhuǎn)化
    Plugin 是解決處理資源加載轉(zhuǎn)化之外的的一些自動(dòng)化工作
    相比于 Loader,Plugin 的能力范圍更寬
    因?yàn)?Loader 只是在加載模塊的范圍工作,而插件的工作范圍可以觸及到 webpack 的每一個(gè)環(huán)節(jié)
class MyPlugin {
  apply(compiler) {
    console.log('MyPlugin 啟動(dòng)');
    // 這里要做的事情就是在emit鉤子上掛載一個(gè)任務(wù)蘑秽,這個(gè)任務(wù)幫我們?nèi)コ虬鬀]有必要的注釋(mode=none情況下)其他鉤子可參考官網(wǎng)
    compiler.hooks.emit.tap('MyPlugin', compilation => {
      // compilation => 可以理解為此次打包的上下文
      for (const name in compilation.assets) {
        // console.log(name)
        // console.log(compilation.assets[name].source())
        if (name.endsWith('.js')) {
          const contents = compilation.assets[name].source();
          const withoutComments = contents.replace(/\/\*\*+\*\//g, '');
          compilation.assets[name] = {
            source: () => withoutComments,
            size: () => withoutComments.length,
          };
        }
      }
    });
  }
}
plugins: [new MyPlugin()];
  • 增強(qiáng) webpack 的開發(fā)體驗(yàn)
// 不使用Webpack Dev Server情況下饺著,自動(dòng)監(jiān)聽打包文件的變化
$ yarn webpack --watch
$ http-server -c-1 dist //or $ browser-sync dist --file  "**/*"

以上方式效率太低,文件不斷的被讀寫操作肠牲,有待優(yōu)化

  • Webpack Dev Server

    編寫源代碼=> webpack 打包=> 運(yùn)行應(yīng)用=> 刷新瀏覽器
    我們可以借助 Webpack Dev Server 來提升開發(fā)體驗(yàn)幼衰,更接近生產(chǎn)環(huán)境的運(yùn)行狀態(tài),同時(shí)也可以設(shè)置 proxy埂材,對(duì)于錯(cuò)誤我們可以使用 souceMap 來快速定位源代碼問題

$ yarn add webpack-dev-server -D
$ yarm webpack-dev-server --open

webpack-dev-server 并不會(huì)將打包結(jié)果放到磁盤中塑顺,暫時(shí)存放到內(nèi)存中,從臨時(shí)內(nèi)存中讀取內(nèi)容發(fā)送給瀏覽器,從而大大提高了效率

  • webpackDevServer 的靜態(tài)資源訪問
devServer: {
  contentBase: './public', //也可以指定數(shù)組標(biāo)識(shí)多個(gè)目錄
}
  • 代理 proxy
    代理方式適用于后端沒有配置 cors 的情況
    如果我們的項(xiàng)目最終上線前后端代碼符合同源策略严拒,也就沒必要設(shè)置 cors 了扬绪,這個(gè)時(shí)候可以通過本地服務(wù)器配置代理的方式實(shí)現(xiàn)跨域請(qǐng)求
  devServer: {
    proxy: {
      '/api': {
        // http://localhost:8080/api/users -> https://api.github.com/api/users
        target: 'https://api.github.com',
        // http://localhost:8080/api/users -> https://api.github.com/users
        pathRewrite: {
          '^/api': '' // 根據(jù)后端接口文件路勁因情況而定,這里只是用github舉例說明
        },
        // 不能使用 localhost:8080 作為請(qǐng)求 GitHub 的主機(jī)名
        changeOrigin: true
      }
    }
  }
// main.js;
// 跨域請(qǐng)求裤唠,雖然 GitHub 支持 CORS挤牛,但是不是每個(gè)服務(wù)端都應(yīng)該支持。
// fetch('https://api.github.com/users')
fetch('/api/users') // http://localhost:8080/api/users
  .then(res => res.json())
  .then(data => {
    data.forEach(item => {
      const li = document.createElement('li');
      li.textContent = item.login;
      ul.append(li);
    });
  });
  • sourceMap

    由于編寫的代碼和運(yùn)行的代碼不一致种蘸,sourceMap 幫我們定位源代碼錯(cuò)誤
    webpack 提供了 12 中 sourceMap 方式墓赴。每種方式的效果和效率不同,效果最好的航瞭,效率最差诫硕,效果最差的,效率最高刊侯,因此我們只需要實(shí)際開發(fā)中符合需求的最佳實(shí)踐
    cheap: 定位到行章办,不定位列
    eval: 定位到文件
    module: 定位 loader 處理前的源代碼
    inline: 把 sourcemap 嵌入到打包文件中,不額外生成對(duì)應(yīng)的.map 文件
    hidden: 會(huì)有錯(cuò)誤信息滨彻,但是不是源文件藕届。開發(fā)第三方包的時(shí)候可以用
    nosources: 看不到源代碼,但是會(huì)有行列信息亭饵,保護(hù)在生產(chǎn)環(huán)境中源代碼不被暴露

devtool: // 開發(fā)環(huán)境  'cheap-module-eval-source-map',
  // 生產(chǎn)環(huán)境 'none',
  // 如果對(duì)自己上線代碼沒有信心 'nosources-source-map'
const HtmlWebpackPlugin = require('html-webpack-plugin');

const allModes = [
  'eval',
  'cheap-eval-source-map',
  'cheap-module-eval-source-map',
  'eval-source-map',
  'cheap-source-map',
  'cheap-module-source-map',
  'inline-cheap-source-map',
  'inline-cheap-module-source-map',
  'source-map',
  'inline-source-map',
  'hidden-source-map',
  'nosources-source-map',
];

module.exports = allModes.map(item => {
  return {
    devtool: item,
    mode: 'none',
    entry: './src/main.js',
    output: {
      filename: `js/${item}.js`,
    },
    module: {
      rules: [
        {
          test: /\.js$/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env'],
            },
          },
        },
      ],
    },
    plugins: [
      new HtmlWebpackPlugin({
        filename: `${item}.html`,
      }),
    ],
  };
});
  • 熱更新(HMR)代替自動(dòng)刷新
    自動(dòng)刷新導(dǎo)致頁面狀態(tài)丟失
    熱更新就是在頁面不跟新的情況下休偶,只將修改的模塊實(shí)時(shí)替換到應(yīng)用中
$ yarn webpack-dev-server --hot
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: './src/main.js',
  output: {
    filename: 'js/bundle.js',
  },
  devtool: 'source-map',
  devServer: {
    hot: true,
    // hotOnly: true // 只使用 HMR,不會(huì) fallback 到 live reloading
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    // ...
  ],
};

默認(rèn)的 HMR 開啟后還需要我們手動(dòng)去處理熱更新的邏輯辜羊。當(dāng)然在 css 文件中由于 cssloader 中已經(jīng)幫我們處理了踏兜,所以我們可以看到修改 css 可以出發(fā)熱跟新
編寫的 js 模塊由于代碼太過靈活,如果沒有框架的約束只冻,wabpack 很難實(shí)現(xiàn)通用的熱更新

  • HMR API
import createEditor from './editor';
import background from './better.png';
import './global.css';

const editor = createEditor();
document.body.appendChild(editor);

const img = new Image();
img.src = background;
document.body.appendChild(img);

// ============ 以下用于處理 HMR庇麦,與業(yè)務(wù)代碼無關(guān) ============

// console.log(createEditor)

if (module.hot) {
  let lastEditor = editor;
  // 處理js模塊的熱更新
  module.hot.accept('./editor', () => {
    // console.log('editor 模塊更新了,需要這里手動(dòng)處理熱替換邏輯')
    // console.log(createEditor)

    const value = lastEditor.innerHTML;
    document.body.removeChild(lastEditor);
    const newEditor = createEditor();
    // 解決文本框狀態(tài)丟失
    newEditor.innerHTML = value;
    document.body.appendChild(newEditor);
    lastEditor = newEditor;
  });
  // 處理img熱更新
  module.hot.accept('./better.png', () => {
    img.src = background;
    console.log(background);
  });
}

以上例子 只是說明 webpack 沒辦法提供通用方案喜德。實(shí)現(xiàn)一個(gè)熱更新原理就是利用 module.hot,HotModuleReplacementPluginApi 提供的這個(gè)垮媒。大部分框架中都集成了 HMR舍悯。

  • 不同環(huán)境的配置文件
// 函數(shù)方式配置
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = (env, argv) => {
  const config = {
    // ...
  };

  if (env === 'production') {
    config.mode = 'production';
    config.devtool = false;
    config.plugins = [
      ...config.plugins,
      new CleanWebpackPlugin(),
      new CopyWebpackPlugin(['public']),
    ];
  }

  return config;
};

文件劃分的配置

// webpack.common.js

module.exports = {};

// webpack.dev.js
const common = require('./webpack.common');
const merge = require('webpack-merge'); // 安裝webpack-merge合并配置
module.exports = merge(common, {
  mode: 'development',
  devtool: 'cheap-eval-module-source-map',
  devServer: {
    hot: true,
    contentBase: 'public',
  },
  plugins: [new webpack.HotModuleReplacementPlugin()],
});

// webpack.prod.js
const merge = require('webpack-merge');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const common = require('./webpack.common');

module.exports = merge(common, {
  mode: 'production',
  plugins: [new CleanWebpackPlugin(), new CopyWebpackPlugin(['public'])],
});
$ yarn webpack --config webpack.prod.js
$ yarn webpack-dev-server --config webpack.dev.js
  • DefinePlugin
    為代碼注入全局成員
    默認(rèn)注入 process.evn.NODE_ENV 常量
plugins: [
  new webpack.DefinePlugin({
    // 值要求的是一個(gè)代碼片段
    API_BASE_URL: JSON.stringify('https://api.example.com'),
  }),
];
  • Tree-shaking
    將未引用代碼去除掉 生產(chǎn)環(huán)境下自動(dòng)開啟
    在其他模式下開啟需要:
 optimization: {
    // 模塊只導(dǎo)出被使用的成員
    usedExports: true,
    // 盡可能合并每一個(gè)模塊到一個(gè)函數(shù)中
    concatenateModules: true, // scope Hoisting
    // 壓縮輸出結(jié)果
    minimize: true
  }
  • Tree-shaking && babel
    由于 Tree-shaking 是基于 ESModule 實(shí)現(xiàn)的,但是 舊版本 babel 中如果用到 preset-env 的插件集合的時(shí)候會(huì)默認(rèn)開啟轉(zhuǎn)化 ESModule 的導(dǎo)入導(dǎo)出語法為 Commonjs 的規(guī)范睡雇。所以導(dǎo)致 Tree-shaking 失效萌衬,新版本已默認(rèn)關(guān)閉

  • sideEffects 新特新
    標(biāo)識(shí)代碼是否有副作用,為 Tree-shaking 提供更大的壓縮空間

// webpack.config.js
optimization: {
  sideEffects: true; // 開啟sideEffects功能
}
// package.json
"siedEffects": false // 標(biāo)識(shí)代碼是否有副作用

副作用需要我們手動(dòng)添加并且謹(jǐn)慎使用它抱,一般用在開發(fā)第三方包中秕豫,當(dāng)我們的代碼有副作用,但是卻配置了以上兩個(gè)屬性,就會(huì)導(dǎo)致程序報(bào)錯(cuò)混移。

// package.json 配置有副作用的文件祠墅,這樣webpack在打包的過程中就不會(huì)忽略這些
"siedEffects" :[
  "./src/extend.js",
  "*/css"
]
  • Code Splitting
    打包成一個(gè)文件導(dǎo)致體積過大,加載時(shí)間過長歌径。
    應(yīng)用啟動(dòng)的首屏并不是所有模塊都工作的
    所以我們需要分包毁嗦,按需加載

    • 多入口打包
      適用于多頁面應(yīng)用
      entry: {
        index: './src/index.js',
        album: './src/album.js'
      },
      output: {
        filename: '[name].bundle.js'
      },
      optimization: {
        splitChunks: {
        // 自動(dòng)提取所有公共模塊到單獨(dú) bundle
          chunks: 'all'
        }
      },
      plugins: [
        new HtmlWebpackPlugin({
          title: '首頁',
          template: './src/index.html',
          filename: 'index.html',
          chunks: ['index'] //  對(duì)不同的頁面指定不同的打包js文件
        }),
        new HtmlWebpackPlugin({
          title: '其他頁面',
          template: './src/album.html',
          filename: 'album.html',
          chunks: ['album']
        })
      ]
    
    • 動(dòng)態(tài)導(dǎo)入 import()
      適用于單頁面應(yīng)用
      在 react 或者 vue 中一般都是通過路由映射組件實(shí)現(xiàn)動(dòng)態(tài)加載
      webpack 會(huì)根據(jù) import()把對(duì)應(yīng)的模塊拆分到不同的輸出文件,根據(jù)加載的需要執(zhí)行不同的 js 文件
    // import posts from './posts/posts'
    // import album from './album/album'
    const render = () => {
    const hash = window.location.hash || '#posts'
    
    const mainElement = document.querySelector('.main')
    
    mainElement.innerHTML = ''
    
    if (hash === '#posts') {
        // mainElement.appendChild(posts())
        import(/_ webpackChunkName: 'components' _/'./posts/posts').then(({ default: posts }) => {
        mainElement.appendChild(posts())
      })
    } else if (hash === '#album') {
        // mainElement.appendChild(album())
        import(/_ webpackChunkName: 'components' _/'./album/album').then(({ default: album }) => {
        mainElement.appendChild(album())
        })
      }
    }
    
    render()
    
    window.addEventListener('hashchange', render)
    
  • 魔法注釋
    通過在 import 語句中加注釋的方式為 webpack 提供打包后的名稱如果設(shè)置一樣則打包到一個(gè)文件

if (hash === '#posts') {
  // mainElement.appendChild(posts())
  import(/* webpackChunkName: 'components' */ './posts/posts').then(
    ({ default: posts }) => {
      mainElement.appendChild(posts());
    }
  );
} else if (hash === '#album') {
  // mainElement.appendChild(album())
  import(/* webpackChunkName: 'components' */ './album/album').then(
    ({ default: album }) => {
      mainElement.appendChild(album());
    }
  );
}
  • MiniCssExtractPlugin
    提取 css 到單個(gè)文件
    需要考慮 css 大小回铛,如果很少寫 css 那么還是采用 stypeLoader 注入到頁面的 style 標(biāo)簽中
 module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          // 'style-loader', // 將樣式通過 style 標(biāo)簽注入
          MiniCssExtractPlugin.loader, // 將樣式通過Link標(biāo)簽方式注入
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin()
  ]
  • webpack 內(nèi)部提供的生產(chǎn)環(huán)境的壓縮只是針對(duì) JS 代碼的狗准。如果想要壓縮其他形式資源,需要單獨(dú)安裝對(duì)應(yīng)的插件
optimization: {
  minimize: [
  // 要使用其他壓縮茵肃,這里要把默認(rèn)的js壓縮的插件也安裝進(jìn)來腔长,是因?yàn)閣ebpack會(huì)覆蓋了 optimization原有的默認(rèn)配置
  // 這里配置的壓縮都只會(huì)在生產(chǎn)環(huán)境起作用,符合我們的預(yù)期验残,不用再去放到webpack.prod.js或者根據(jù)環(huán)境變量判斷
  new TreserWebpackPlugin(),
  // 這里以壓縮css為例捞附,其他的參見官網(wǎng)
  new OptimizeCssAssetsWebpackPlugin()]
}
  • 輸出文件名稱 Hash
    一般我們部署前端資源文件的時(shí)候,都會(huì)開啟靜態(tài)資源緩存胚膊,避免每次都請(qǐng)求資源故俐,整體應(yīng)用的響應(yīng)速度就會(huì)大幅度提升,不過也會(huì)有問題紊婉,當(dāng)我們緩存時(shí)間設(shè)置過長药版,我們的應(yīng)用更新過后,瀏覽器并不會(huì)及時(shí)更新喻犁。這就需要我們?cè)谏a(chǎn)環(huán)境中需要給文件添加 Hash 值槽片,全新的文件名就是全新的請(qǐng)求,也就避免了上述問題
    hash: 只要內(nèi)容修改肢础,所有文件 hash 都會(huì)跟新
    contenthash: 文件級(jí)別的 hash,當(dāng)前修改的文件以及被引用的文件 hash 會(huì)被動(dòng)跟新
    chunk: 當(dāng)文件內(nèi)容改變还栓,只修改當(dāng)前同類的 hash 值
  output: {
    filename: '[name]-[contenthash:8].bundle.js'
  },

還有一些其他的配置項(xiàng)比如 preformance target externals resolve other option 我們只需要查閱官方文檔即可,另外還需要多理解 manifest 和 runtime 這樣的 webpack 概念

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市传轰,隨后出現(xiàn)的幾起案子剩盒,更是在濱河造成了極大的恐慌,老刑警劉巖慨蛙,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辽聊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡期贫,警方通過查閱死者的電腦和手機(jī)跟匆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來通砍,“玉大人玛臂,你說我怎么就攤上這事。” “怎么了迹冤?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵讽营,是天一觀的道長。 經(jīng)常有香客問我叁巨,道長斑匪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任锋勺,我火速辦了婚禮蚀瘸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘庶橱。我一直安慰自己贮勃,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布苏章。 她就那樣靜靜地躺著寂嘉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪枫绅。 梳的紋絲不亂的頭發(fā)上泉孩,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音并淋,去河邊找鬼寓搬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛县耽,可吹牛的內(nèi)容都是我干的句喷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼兔毙,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼唾琼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起澎剥,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤锡溯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后哑姚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趾唱,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年蜻懦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夕晓。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡宛乃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情征炼,我是刑警寧澤析既,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站谆奥,受9級(jí)特大地震影響眼坏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜酸些,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一宰译、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧魄懂,春花似錦沿侈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至填帽,卻和暖如春蛛淋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背篡腌。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國打工褐荷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哀蘑。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓诚卸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親绘迁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子合溺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353