webpack base

1. webpack介紹

  • 本質(zhì)上喇颁,webpack 是一個(gè)用于現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包工具截粗。當(dāng) webpack 處理應(yīng)用程序時(shí)屯曹,它會(huì)在內(nèi)部構(gòu)建一個(gè) 依賴圖(dependency graph)脊另,此依賴圖對應(yīng)映射到項(xiàng)目所需的每個(gè)模塊,并生成一個(gè)或多個(gè) bundle

1.1 安裝

npm install  webpack webpack-cli --save-dev

1.2 入口(entry)

  • 入口起點(diǎn)(entry point)指示 webpack 應(yīng)該使用哪個(gè)模塊纹坐,來作為構(gòu)建其內(nèi)部 依賴圖(dependency graph) 的開始。進(jìn)入入口起點(diǎn)后舞丛,webpack 會(huì)找出有哪些模塊和庫是入口起點(diǎn)(直接和間接)依賴的
  • 默認(rèn)值是 ./src/index.js耘子,但你可以通過在 webpack configuration 中配置 entry 屬性,來指定一個(gè)(或多個(gè))不同的入口起點(diǎn)

1.2.1 src\index.js

let title = require('./title.txt');
document.write(title.default);

1.2.2 webpack.config.js

const path = require('path');
module.exports = {
  entry: './src/index.js',
};

1.3 輸出(output)

  • output 屬性告訴 webpack 在哪里輸出它所創(chuàng)建的 bundle瓷马,以及如何命名這些文件
  • 主要輸出文件的默認(rèn)值是 ./dist/main.js拴还,其他生成文件默認(rèn)放置在 ./dist 文件夾中。

webpack.config.js

const path = require('path');
module.exports = {
  entry: './src/index.js',
+  output: {
+    path: path.resolve(__dirname, 'dist'),
+    filename: 'main.js'
+  }
};

1.4 loader

  • webpack 只能理解 JavaScriptJSON 文件
  • loader 讓 webpack 能夠去處理其他類型的文件欧聘,并將它們轉(zhuǎn)換為有效模塊片林,以供應(yīng)用程序使用,以及被添加到依賴圖中

webpack.config.js

const path = require('path');
module.exports = {
  mode: 'development',
  devtool:false,
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js'
  },
+  module: {
+    rules: [
+      { test: /\.txt$/, use: 'raw-loader' }
+    ]
+  }
};

1.5 插件(plugin)

  • loader 用于轉(zhuǎn)換某些類型的模塊怀骤,而插件則可以用于執(zhí)行范圍更廣的任務(wù)费封。包括:打包優(yōu)化,資源管理蒋伦,注入環(huán)境變量

1.5.1 src\index.html

src\index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>webpack5</title>
</head>
<body>
</body>
</html>

1.5.2 webpack.config.js

const path = require('path');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',
  devtool:false,
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  },
+  plugins: [
+    new HtmlWebpackPlugin({template: './src/index.html'})
+  ]
};

1.6 模式(mode)

  • 日常的前端開發(fā)工作中弓摘,一般都會(huì)有兩套構(gòu)建環(huán)境
  • 一套開發(fā)時(shí)使用,構(gòu)建結(jié)果用于本地開發(fā)調(diào)試痕届,不進(jìn)行代碼壓縮韧献,打印 debug 信息末患,包含 sourcemap 文件
  • 一套構(gòu)建后的結(jié)果是直接應(yīng)用于線上的,即代碼都是壓縮后锤窑,運(yùn)行時(shí)不打印 debug 信息璧针,靜態(tài)文件不包括 sourcemap
  • webpack 4.x 版本引入了 mode 的概念
  • 當(dāng)你指定使用 production mode 時(shí),默認(rèn)會(huì)啟用各種性能優(yōu)化的功能渊啰,包括構(gòu)建結(jié)果優(yōu)化以及 webpack 運(yùn)行性能優(yōu)化
  • 而如果是 development mode 的話探橱,則會(huì)開啟 debug 工具,運(yùn)行時(shí)打印詳細(xì)的錯(cuò)誤信息绘证,以及更加快速的增量編譯構(gòu)建
選項(xiàng) 描述
development 會(huì)將 process.env.NODE_ENV 的值設(shè)為 development隧膏。啟用 NamedChunksPlugin 和 NamedModulesPlugin
production 會(huì)將 process.env.NODE_ENV 的值設(shè)為 production。啟用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin

1.6.1 環(huán)境差異

  • 開發(fā)環(huán)境
    • 需要生成 sourcemap 文件
    • 需要打印 debug 信息
    • 需要 live reload 或者 hot reload 的功能
  • 生產(chǎn)環(huán)境
    • 可能需要分離 CSS 成單獨(dú)的文件嚷那,以便多個(gè)頁面共享同一個(gè) CSS 文件
    • 需要壓縮 HTML/CSS/JS 代碼
    • 需要壓縮圖片
  • 其默認(rèn)值為 production

1.6.2 區(qū)分環(huán)境

  • --mode用來設(shè)置模塊內(nèi)的process.env.NODE_ENV
  • --env用來設(shè)置webpack配置文件的函數(shù)參數(shù)
  • cross-env用來設(shè)置node環(huán)境的process.env.NODE_ENV
  • DefinePlugin用來設(shè)置模塊內(nèi)的全局變量
1.6.2.1 命令行配置1
  • webpack的mode默認(rèn)為production
  • webpack serve的mode默認(rèn)為development
  • 可以在模塊內(nèi)通過process.env.NODE_ENV獲取當(dāng)前的環(huán)境變量,無法在webpack配置文件中獲取此變量
  "scripts": {
    "build": "webpack",
    "start": "webpack serve"
  },

index.js

console.log(process.env.NODE_ENV);// development | production

webpack.config.js

console.log('NODE_ENV',process.env.NODE_ENV);// undefined

1.6.2.2 命令行配置2
  • 同配置1

    "scripts": {
      "build": "webpack --mode=production",
      "start": "webpack --mode=development serve"
    },
    
    
1.6.2.3 命令行配置
  • 無法在模塊內(nèi)通過process.env.NODE_ENV訪問
  • 可以通過webpack 配置文件中中通過函數(shù)獲取當(dāng)前環(huán)境變量
"scripts": {
   "dev": "webpack serve --env=development",
   "build": "webpack --env=production",
}

index.js

 console.log(process.env.NODE_ENV);// undefined

webpack.config.js

console.log('NODE_ENV',process.env.NODE_ENV);// undefined

module.exports = (env,argv) => {
  console.log('env',env);// development | production
};

1.6.2.4 mode配置
  • 和命令行配置2一樣

    module.exports = {
    mode: 'development'
    }
    
    
1.6.2.5 DefinePlugin
  • 設(shè)置全局變量(不是window),所有模塊都能讀取到該變量的值
  • 可以在任意模塊內(nèi)通過 process.env.NODE_ENV 獲取當(dāng)前的環(huán)境變量
  • 但無法在node環(huán)境(webpack 配置文件中)下獲取當(dāng)前的環(huán)境變量
plugins:[
   new webpack.DefinePlugin({
      'process.env.NODE_ENV':JSON.stringify('development'),
      'NODE_ENV':JSON.stringify('production'),
   })
]   

index.js

console.log(NODE_ENV);//  production

webpack.config.js

console.log('process.env.NODE_ENV',process.env.NODE_ENV);// undefined
console.log('NODE_ENV',NODE_ENV);// error 0怼!车酣!

1.6.2.6 cross-env
  • 只能設(shè)置node環(huán)境下的變量NODE_ENV

package.json

"scripts": {
  "build": "cross-env NODE_ENV=development webpack"
}

webpack.config.js

console.log('process.env.NODE_ENV',process.env.NODE_ENV);// development

2. 開發(fā)環(huán)境配置

2.1 開發(fā)服務(wù)器

2.1.1 安裝服務(wù)器

npm install webpack-dev-server --save-dev

2.1.2 webpack.config.js

類別 配置名稱 描述
output path 指定輸出到硬盤上的目錄
output publicPath 表示的是打包生成的index.html文件里面引用資源的前綴
devServer publicPath 表示的是打包生成的靜態(tài)文件所在的位置(若是devServer里面的publicPath沒有設(shè)置曲稼,則會(huì)認(rèn)為是output里面設(shè)置的publicPath的值)
devServer contentBase 用于配置提供額外靜態(tài)文件內(nèi)容的目錄

2.1.3 webpack.config.js

module.exports = {
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    compress: true,
    port: 8080,
    open: true
  },
}

2.1.4 package.json

  "scripts": {
    "build": "webpack",
+   "start": "webpack serve"
  }

2.2. 支持CSS

2.2.1 安裝模塊

cnpm i style-loader css-loader -D

  {
      test:/\.css$/,
      //最后一個(gè)loader,就上面最左邊的loader一定要返回一個(gè)JS腳本
      use:['style-loader',{
          loader:'css-loader',
          options:{importLoaders:1}
      },'postcss-loader']
  },

#less-container{
    transform:rotate(7deg);
}

2.2.2 webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',
  devtool:false,
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' },
+     { test: /\.css$/, use: ['style-loader','css-loader'] }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
};

2.2.3 src\bg.css

src\bg.css

body{
    background-color: green;
}

2.2.4 src\index.css

src\index.css

@import "./bg.css";
body{
    color:red;
}

2.2.5 src\index.js

src\index.js

+import './index.css';
let title = require('./title.txt');
document.write(title.default);

2.3. 支持less和sass

2.3.1 安裝

npm i less less-loader -D
npm i node-sass sass-loader -D

2.3.2 webpack.config.js

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',
  devtool: false,
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' },
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },
+     { test: /\.less$/, use: ['style-loader','css-loader', 'less-loader'] },
+     { test: /\.scss$/, use: ['style-loader','css-loader', 'sass-loader'] }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({ template: './src/index.html' })
  ]
};

2.3.3 src\index.html

src\index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>webpack5</title>
</head>
<body>
+  <div id="less-container">less-container</div>
+  <div id="sass-container">sass-container</div>
</body>
</html>

2.3.4 src\index.js

src\index.js

import './index.css';
+import './less.less';
+import './sass.scss';
let title = require('./title.txt');
document.write(title.default);

2.3.5 src\less.less

src\less.less

@color:blue;
#less-container{
    color:@color;
}

2.3.6 src\sass.scss

src\sass.scss

$color:orange;
#sass-container{
    color:$color;
}

2.4 CSS兼容性

  • 為了瀏覽器的兼容性,有時(shí)候我們必須加入-webkit,-ms,-o,-moz這些前綴
    • Trident內(nèi)核:主要代表為IE瀏覽器, 前綴為-ms
    • Gecko內(nèi)核:主要代表為Firefox, 前綴為-moz
    • Presto內(nèi)核:主要代表為Opera, 前綴為-o
    • Webkit內(nèi)核:產(chǎn)要代表為Chrome和Safari, 前綴為-webkit
  • 偽元素::placeholder可以選擇一個(gè)表單元素的占位文本湖员,它允許開發(fā)者和設(shè)計(jì)師自定義占位文本的樣式贫悄。

2.4.1 安裝

npm i postcss-loader postcss-preset-env -D

2.4.2 postcss.config.js

postcss.config.js

let postcssPresetEnv = require('postcss-preset-env');
module.exports={
    plugins:[postcssPresetEnv({
        browsers: 'last 5 version'
    })]
}

2.4.3 webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',
  devtool: false,
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' },
+     { test: /\.css$/, use: ['style-loader', 'css-loader','postcss-loader'] },
+     { test: /\.less$/, use: ['style-loader','css-loader','postcss-loader','less-loader'] },
+     { test: /\.scss$/, use: ['style-loader','css-loader','postcss-loader','sass-loader'] }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({ template: './src/index.html' })
  ]
};

2.4.4 src\index.css

src\index.css

@import "./bg.css";
body{
    color:red;
}
#logo{
    width:540px;
    height:258px;
    background-image: url(./assets/logo.png);
    background-size: cover;
}
+::placeholder {
+    color: red;
+}

2.4.5 src\index.html

src\index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>webpack5</title>
</head>
<body>
  <div id="less-container">less-container</div>
  <div id="sass-container">sass-container</div>
  <div id="logo"></div>
+  <input placeholder="請輸入"/>
</body>
</html>

2.4.6 package.json

{
+  "browserslist": {
+    "development": [
+      "last 1 chrome version",
+      "last 1 firefox version",
+      "last 1 safari version"
+    ],
+    "production": [
+      ">0.2%"
+    ]
+  }
+}

2.5 支持圖片

2.5.1 安裝

  • file-loader 解決CSS等文件中的引入圖片路徑問題
  • url-loader 當(dāng)圖片小于limit的時(shí)候會(huì)把圖片BASE64編碼,大于limit參數(shù)的時(shí)候還是使用file-loader進(jìn)行拷貝
cnpm i file-loader url-loader html-loader -D

2.5.2 webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',
  devtool: false,
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' },
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },
      { test: /\.less$/, use: ['style-loader','css-loader', 'less-loader'] },
      { test: /\.scss$/, use: ['style-loader','css-loader', 'sass-loader'] },
+     { test: /\.(jpg|png|bmp|gif|svg)$/, 
+        use: [{
+          loader: 'url-loader', 
+          options: {
+            esModule: false,
+            name: '[hash:10].[ext]',
+            limit: 8*1024,
+          }
+        }]
+     }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({ template: './src/index.html' })
  ]
};

2.5.3 src\index.html

src\index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>webpack5</title>
</head>
<body>
  <div id="less-container">less-container</div>
  <div id="sass-container">sass-container</div>
+ <div id="logo"></div>
</body>
</html>

2.5.4 src\index.js

src\index.js

import './index.css';
import './less.less';
import './sass.scss';
let title = require('./title.txt');
document.write(title.default);
+let logo=require('./assets/logo.png');
+let img=new Image();
+img.src=logo.default;
+document.body.appendChild(img);

2.6 JS兼容性處理

  • Babel其實(shí)是一個(gè)編譯JavaScript的平臺(tái),可以把ES6/ES7,React的JSX轉(zhuǎn)義為ES5

2.6.1 @babel/preset-env

  • Babel默認(rèn)只轉(zhuǎn)換新的最新ES語法,比如箭頭函數(shù)

2.6.1.1 安裝依賴

cnpm i babel-loader @babel/core @babel/preset-env @babel/preset-react  -D
cnpm i @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties -D

2.6.1.2 webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  mode: 'development',
  devtool: false,
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
    publicPath: '/'
  },
  module: {
    rules: [
+      {
+        test: /\.jsx?$/,
+        use: {
+          loader: 'babel-loader',
+          options: {
+            presets: [["@babel/preset-env",{
+              targets: "> 0.25%, not dead",
+            }], '@babel/preset-react'],
+            plugins: [
+              ['@babel/plugin-proposal-decorators', { legacy: true }],
+              ['@babel/plugin-proposal-class-properties', { loose: true }],
+            ],
+          },
+        },
+      },
      { test: /\.txt$/, use: 'raw-loader' },
      { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'] },
      { test: /\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'] },
      { test: /\.scss$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'] },
      {
        test: /\.(jpg|png|bmp|gif|svg)$/, use: [{
          loader: 'url-loader', options: {
            limit: 10,
            outputPath: 'images',
            publicPath: '/images'
          }
        }]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({ template: './src/index.html' }),
    new MiniCssExtractPlugin({
      filename: 'css/[name].css'
    }),
  ]
};

2.6.1.3 src\index.js

src\index.js

+function readonly(target,key,descriptor) {
+    descriptor.writable=false;
+}
+
+class Person{
+    @readonly PI=3.14;
+}
+let p1=new Person();
+p1.PI=3.15;
+console.log(p1)

2.6.1.4 jsconfig.json

jsconfig.json

{
    "compilerOptions": {
        "experimentalDecorators": true
    }
}

2.7 ESLint代碼校驗(yàn)

2.7.1 安裝

cnpm install eslint eslint-loader babel-eslint --D

2.7.2 webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  mode: 'development',
  devtool: false,
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
    publicPath: '/'
  },
  module: {
    rules: [
+      {
+        test: /\.jsx?$/,
+        loader: 'eslint-loader',
+        enforce: 'pre',
+        options: { fix: true },
+        exclude: /node_modules/,
+      },
      {
        test: /\.jsx?$/,
        use: {
          loader: 'babel-loader',
          options: {
            "presets": ["@babel/preset-env"],
            "plugins": [
              ["@babel/plugin-proposal-decorators", { "legacy": true }],
              ["@babel/plugin-proposal-class-properties", { "loose": true }]
            ]
          }
        },
        include: path.join(__dirname, 'src'),
        exclude: /node_modules/
      },
      { test: /\.txt$/, use: 'raw-loader' },
      { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'] },
      { test: /\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'] },
      { test: /\.scss$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'] },
      {
        test: /\.(jpg|png|bmp|gif|svg)$/, use: [{
          loader: 'url-loader', options: {
            limit: 10,
            outputPath: 'images',
            publicPath: '/images'
          }
        }]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({ template: './src/index.html' }),
    new MiniCssExtractPlugin({
      filename: 'css/[name].css'
    }),
  ]
};

2.7.3 src\index.html

src\index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>webpack5</title>
</head>
<body>
+  <div id="root"></div>
</body>
</html>

2.7.4 src\index.js

src\index.js

+import React from "react";
+import ReactDOM from "react-dom";
+ReactDOM.render("hello",document.getElementById("root"));
+
+function readonly(target,key,descriptor) {
+    descriptor.writable=false;
+}
+
+class Person{
+    @readonly PI=3.14;
+}
+let p1=new Person();
+p1.PI=3.15;

2.7.5 .eslintrc.js

.eslintrc.js

module.exports = {
    root: true,
    parser:"babel-eslint",
    //指定解析器選項(xiàng)
    parserOptions: {
        sourceType: "module",
        ecmaVersion: 2015
    },
    //指定腳本的運(yùn)行環(huán)境
    env: {
        browser: true,
    },
    // 啟用的規(guī)則及其各自的錯(cuò)誤級別
    rules: {
        "indent": "off",//縮進(jìn)風(fēng)格
        "quotes":  "off",//引號(hào)類型 
        "no-console": "error",//禁止使用console
    }
}

2.7.6 airbnb

cnpm i eslint-config-airbnb eslint-loader eslint eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks and eslint-plugin-jsx-a11y -D

eslintrc.js

module.exports = {
    "parser":"babel-eslint",
    "extends":"airbnb",
    "rules":{
        "semi":"error",
        "no-console":"off",
        "linebreak-style":"off",
        "eol-last":"off"
        //"indent":["error",2]
    },
    "env":{
        "browser":true,
        "node":true
    }
}

2.7.7 自動(dòng)修復(fù)

  • 安裝vscode的eslint插件
  • 配置自動(dòng)修復(fù)參數(shù)

.vscode\settings.json

{
    "eslint.validate": [
        "javascript",
        "javascriptreact",
        "typescript",
        "typescriptreact"
    ],
    "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true
    }
  }

2.8 sourcemap

  • sourcemap是為了解決開發(fā)代碼與實(shí)際運(yùn)行代碼不一致時(shí)幫助我們debug到原始開發(fā)代碼的技術(shù)
  • webpack通過配置可以自動(dòng)給我們source maps文件娘摔,map文件是一種對應(yīng)編譯文件和源文件的方法
  • whyeval可以單獨(dú)緩存map窄坦,重建性能更高
  • source-map

2.8.1 配置項(xiàng)

類型 含義
source-map 原始代碼 最好的sourcemap質(zhì)量有完整的結(jié)果,但是會(huì)很慢
eval-source-map 原始代碼 同樣道理凳寺,但是最高的質(zhì)量和最低的性能
cheap-module-eval-source-map 原始代碼(只有行內(nèi)) 同樣道理鸭津,但是更高的質(zhì)量和更低的性能
cheap-eval-source-map 轉(zhuǎn)換代碼(行內(nèi)) 每個(gè)模塊被eval執(zhí)行,并且sourcemap作為eval的一個(gè)dataurl
eval 生成代碼 每個(gè)模塊都被eval執(zhí)行肠缨,并且存在@sourceURL,帶eval的構(gòu)建模式能cache SourceMap
cheap-source-map 轉(zhuǎn)換代碼(行內(nèi)) 生成的sourcemap沒有列映射逆趋,從loaders生成的sourcemap沒有被使用
cheap-module-source-map 原始代碼(只有行內(nèi)) 與上面一樣除了每行特點(diǎn)的從loader中進(jìn)行映射

2.8.2 關(guān)鍵字

  • 看似配置項(xiàng)很多, 其實(shí)只是五個(gè)關(guān)鍵字eval晒奕、source-map闻书、cheap、module和inline的任意組合
  • 關(guān)鍵字可以任意組合脑慧,但是有順序要求
關(guān)鍵字 含義
eval 使用eval包裹模塊代碼
source-map 產(chǎn)生.map文件
cheap 不包含列信息(關(guān)于列信息的解釋下面會(huì)有詳細(xì)介紹)也不包含loader的sourcemap
module 包含loader的sourcemap(比如jsx to js 魄眉,babel的sourcemap),否則無法定義源文件
inline 將.map作為DataURI嵌入,不單獨(dú)生成.map文件

2.8.3 webpack.config.js

module.exports = {
  devtool: 'source-map',
  devtool: 'eval-source-map',
  devtool: 'cheap-module-eval-source-map',
  devtool: 'cheap-eval-source-map',
  devtool: 'eval',
  devtool: 'cheap-source-map',
  devtool: 'cheap-module-source-map',
}

2.8.4 組合規(guī)則

  • [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
  • source-map 單獨(dú)在外部生成完整的sourcemap文件闷袒,并且在目標(biāo)文件里建立關(guān)聯(lián),能提示錯(cuò)誤代碼的準(zhǔn)確原始位置
  • inline-source-map 以base64格式內(nèi)聯(lián)在打包后的文件中坑律,內(nèi)聯(lián)構(gòu)建速度更快,也能提示錯(cuò)誤代碼的準(zhǔn)確原始位置
  • hidden-source-map 會(huì)在外部生成sourcemap文件,但是在目標(biāo)文件里沒有建立關(guān)聯(lián),不能提示錯(cuò)誤代碼的準(zhǔn)確原始位置
  • eval-source-map 會(huì)為每一個(gè)模塊生成一個(gè)單獨(dú)的sourcemap文件進(jìn)行內(nèi)聯(lián),并使用eval執(zhí)行
  • nosources-source-map 也會(huì)在外部生成sourcemap文件,能找到源始代碼位置囊骤,但源代碼內(nèi)容為空
  • cheap-source-map 外部生成sourcemap文件,不包含列和loader的map
  • cheap-module-source-map 外部生成sourcemap文件,不包含列的信息但包含loader的map

2.8.5 最佳實(shí)踐

2.8.5.1 開發(fā)環(huán)境
  • 我們在開發(fā)環(huán)境對sourceMap的要求是:速度快晃择,調(diào)試更友好
  • 要想速度快 推薦 eval-cheap-source-map
  • 如果想調(diào)試更友好 cheap-module-source-map
  • 折中的選擇就是 eval-source-map
2.8.5.2 生產(chǎn)環(huán)境
  • 首先排除內(nèi)聯(lián)冀值,因?yàn)橐环矫嫖覀兞穗[藏源代碼,另一方面要減少文件體積
  • 要想調(diào)試友好 sourcemap>cheap-source-map/cheap-module-source-map>hidden-source-map/nosources-sourcemap
  • 要想速度快 優(yōu)先選擇cheap
  • 折中的選擇就是 hidden-source-map

2.8.6 調(diào)試代碼

2.8.6.1 測試環(huán)境調(diào)試
  • source-map-dev-tool-plugin實(shí)現(xiàn)了對 source map 生成宫屠,進(jìn)行更細(xì)粒度的控制
    • filename (string):定義生成的 source map 的名稱(如果沒有值將會(huì)變成 inlined)池摧。
    • append (string):在原始資源后追加給定值。通常是 #sourceMappingURL 注釋激况。[url] 被替換成 source map 文件的 URL
  • 市面上流行兩種形式的文件指定,分別是以 @#符號(hào)開頭的,@開頭的已經(jīng)被廢棄
enablesourcemap

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
+const FileManagerPlugin = require('filemanager-webpack-plugin');
+const webpack = require('webpack');

module.exports = {
  mode: 'none',
  devtool: false,
  entry: './src/index.js',
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin(),
    ],
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
    publicPath: '/',
  },
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    compress: true,
    port: 8080,
    open: true,
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: 'eslint-loader',
        enforce: 'pre',
        options: { fix: true },
        exclude: /node_modules/,
      },
      {
        test: /\.jsx?$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [[
              '@babel/preset-env',
              {
                useBuiltIns: 'usage', // 按需要加載polyfill
                corejs: {
                  version: 3, // 指定core-js版本
                },
                targets: { // 指定要兼容到哪些版本的瀏覽器
                  chrome: '60',
                  firefox: '60',
                  ie: '9',
                  safari: '10',
                  edge: '17',
                },
              },
            ], '@babel/preset-react'],
            plugins: [
              ['@babel/plugin-proposal-decorators', { legacy: true }],
              ['@babel/plugin-proposal-class-properties', { loose: true }],
            ],
          },
        },
        include: path.join(__dirname, 'src'),
        exclude: /node_modules/,
      },
      { test: /\.txt$/, use: 'raw-loader' },
      { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'] },
      { test: /\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader'] },
      { test: /\.scss$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader'] },
      {
        test: /\.(jpg|png|bmp|gif|svg)$/,
        use: [{
          loader: 'url-loader',
          options: {
            esModule: false,
            name: '[hash:10].[ext]',
            limit: 8 * 1024,
            outputPath: 'images',
            publicPath: '/images',
          },
        }],
      },
      {
        test: /\.html$/,
        loader: 'html-loader',
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true,
      },
    }),
    new MiniCssExtractPlugin({
      filename: 'css/[name].css',
    }),
    new OptimizeCssAssetsWebpackPlugin(),
+    new webpack.SourceMapDevToolPlugin({
+      append: '\n//# sourceMappingURL=http://127.0.0.1:8081/[url]',
+      filename: '[file].map',
+    }),
+    new FileManagerPlugin({
+      events: {
+        onEnd: {
+          copy: [{
+            source: './dist/*.map',
+            destination: 'C:/aprepare/zhufengwebpack2021/1.basic/sourcemap',
+          }],
+          delete: ['./dist/*.map'],
+        },
+      },
+    }),
  ],
};

2.8.6.2 生產(chǎn)環(huán)境調(diào)試
  • webpack打包仍然生成sourceMap膘魄,但是將map文件挑出放到本地服務(wù)器乌逐,將不含有map文件的部署到服務(wù)器
addsourcemapfile.png

2.9 打包第三方類庫

2.9.1 直接引入

import _ from 'lodash';
alert(_.join(['a','b','c'],'@'));

2.9.2 插件引入

  • webpack配置ProvidePlugin后,在使用時(shí)將不再需要import和require進(jìn)行引入创葡,直接使用即可
  • _ 函數(shù)會(huì)自動(dòng)添加到當(dāng)前模塊的上下文浙踢,無需顯示聲明
+ new webpack.ProvidePlugin({
+     _:'lodash'
+ })

沒有全局的$函數(shù),所以導(dǎo)入依賴全局變量的插件依舊會(huì)失敗

2.9.3 expose-loader

  • expose-loader可以把模塊添加到全局對象上灿渴,在調(diào)試的時(shí)候比較有用
  • The expose loader adds modules to the global object. This is useful for debugging
  • 不需要任何其他的插件配合洛波,只要將下面的代碼添加到所有的loader之前
  module: {
    rules: [
+      {
+          test: require.resolve('lodash'),
+          loader: 'expose-loader',
+          options: {
+              exposes: {
+                  globalName: '_',
+                  override: true,
+              },
+          },
+      }
    ]
  }

2.9.4 externals

如果我們想引用一個(gè)庫,但是又不想讓webpack打包骚露,并且又不影響我們在程序中以CMD蹬挤、AMD或者window/global全局等方式進(jìn)行使用,那就可以通過配置externals

 const jQuery = require("jquery");
 import jQuery from 'jquery';

<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>

+externals: {
+  lodash: '_',
+},
  module: {

2.10 watch

  • 當(dāng)代碼發(fā)生修改后可以自動(dòng)重新編譯

    module.exports = {
      //默認(rèn)false,也就是不開啟
      watch:true,
      //只有開啟監(jiān)聽模式時(shí)棘幸,watchOptions才有意義
      watchOptions:{
          //默認(rèn)為空焰扳,不監(jiān)聽的文件或者文件夾,支持正則匹配
          ignored:/node_modules/,
          //監(jiān)聽到變化發(fā)生后會(huì)等300ms再去執(zhí)行误续,默認(rèn)300ms
          aggregateTimeout:300,
          //判斷文件是否發(fā)生變化是通過不停的詢問文件系統(tǒng)指定議是有變化實(shí)現(xiàn)的吨悍,默認(rèn)每秒問1000次
          poll:1000
      }
    }
    
    
  • webpack定時(shí)獲取文件的更新時(shí)間,并跟上次保存的時(shí)間進(jìn)行比對蹋嵌,不一致就表示發(fā)生了變化,poll就用來配置每秒問多少次

  • 當(dāng)檢測文件不再發(fā)生變化育瓜,會(huì)先緩存起來,等待一段時(shí)間后之后再通知監(jiān)聽者栽烂,這個(gè)等待時(shí)間通過aggregateTimeout配置

  • webpack只會(huì)監(jiān)聽entry依賴的文件

  • 我們需要盡可能減少需要監(jiān)聽的文件數(shù)量和檢查頻率躏仇,當(dāng)然頻率的降低會(huì)導(dǎo)致靈敏度下降

2.11 添加商標(biāo)

+ new webpack.BannerPlugin('珠峰架構(gòu)'),

2.12 拷貝靜態(tài)文件

npm i copy-webpack-plugin -D

+const CopyWebpackPlugin = require('copy-webpack-plugin');
+new CopyWebpackPlugin({
+  patterns: [{
+    from: path.resolve(__dirname,'src/static'),//靜態(tài)資源目錄源地址
+    to: path.resolve(__dirname,'dist/static'), //目標(biāo)地址,相對于output的path目錄
+  }],
+}),

2.13 clean-webpack-plugin

npm i  clean-webpack-plugin -D

+ const {CleanWebpackPlugin} = require('clean-webpack-plugin');
plugins:[
+ new CleanWebpackPlugin({cleanOnceBeforeBuildPatterns: ['**/*'],})
]

2.14 服務(wù)器代理

如果你有單獨(dú)的后端開發(fā)服務(wù)器 API愕鼓,并且希望在同域名下發(fā)送 API 請求 钙态,那么代理某些 URL 會(huì)很有用。

2.14.1 不修改路徑

devServer: {
  proxy: {
    "/api": 'http://localhost:3000'
  }
}

2.14.2 修改路徑

devServer: {
  proxy: {
      "/api": {
       target: 'http://localhost:3000',
       pathRewrite:{"^/api":""}        
      }            
  }
}

2.14.3 before after

before 在 webpack-dev-server 靜態(tài)資源中間件處理之前册倒,可以用于攔截部分請求返回特定內(nèi)容,或者實(shí)現(xiàn)簡單的數(shù)據(jù) mock磺送。

devServer: {
  before(app){
    app.get('/api/users', function(req, res) { 
       res.json([{id:1,name:'zhufeng'}])
    })
  }
}

2.14.4 webpack-dev-middleware

webpack-dev-middleware就是在 Express 中提供 webpack-dev-server 靜態(tài)服務(wù)能力的一個(gè)中間件

npm install webpack-dev-middleware --save-dev

const express = require('express');
const app = express();
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackOptions = require('./webpack.config');
webpackOptions.mode = 'development';
const compiler = webpack(webpackOptions);
app.use(webpackDevMiddleware(compiler, {}));
app.listen(3000);

  • webpack-dev-server 的好處是相對簡單驻子,直接安裝依賴后執(zhí)行命令即可
  • 而使用webpack-dev-middleware的好處是可以在既有的 Express 代碼基礎(chǔ)上快速添加 webpack-dev-server 的功能灿意,同時(shí)利用 Express 來根據(jù)需要添加更多的功能,如 mock 服務(wù)崇呵、代理 API 請求等

3.生產(chǎn)環(huán)境

3.1 提取CSS

  • 因?yàn)镃SS的下載和JS可以并行,當(dāng)一個(gè)HTML文件很大的時(shí)候缤剧,我們可以把CSS單獨(dú)提取出來加載

3.1.1 安裝

cnpm install --save-dev mini-css-extract-plugin

3.1.2 webpack.config.js

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  mode: 'development',
  devtool: false,
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
+    publicPath: '/'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' },
+      { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] },
+      { test: /\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'] },
+      { test: /\.scss$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'] },
       { test: /\.(jpg|png|bmp|gif|svg)$/, 
        use: [{
          loader: 'url-loader', 
          options: {
            esModule: false,
            name: '[hash:10].[ext]',
            limit: 8*1024
          }
        }]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({ template: './src/index.html' }),
+    new MiniCssExtractPlugin({
+      filename: '[name].css'
+    })
  ]
};

3.2 指定圖片和CSS目錄

3.2.1 webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  mode: 'development',
  devtool: false,
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
    publicPath: '/'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' },
      { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] },
      { test: /\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'] },
      { test: /\.scss$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'] },
      { test: /\.(jpg|png|bmp|gif|svg)$/, 
        use: [{
          loader: 'url-loader', 
          options: {
            esModule: false,
            name: '[hash:10].[ext]',
            limit: 8*1024,
+           outputPath: 'images',
+           publicPath: '/images'
          }
        }]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({ template: './src/index.html' }),
    new MiniCssExtractPlugin({
+      filename: 'css/[name].css'
    }),
  ]
};

3.3 hash、chunkhash和contenthash

  • 文件指紋是指打包后輸出的文件名和后綴
  • hash一般是結(jié)合CDN緩存來使用域慷,通過webpack構(gòu)建之后荒辕,生成對應(yīng)文件名自動(dòng)帶上對應(yīng)的MD5值。如果文件內(nèi)容改變的話犹褒,那么對應(yīng)文件哈希值也會(huì)改變抵窒,對應(yīng)的HTML引用的URL地址也會(huì)改變,觸發(fā)CDN服務(wù)器從源服務(wù)器上拉取對應(yīng)數(shù)據(jù)叠骑,進(jìn)而更新本地緩存李皇。

指紋占位符

占位符名稱 含義
ext 資源后綴名
name 文件名稱
path 文件的相對路徑
folder 文件所在的文件夾
hash 每次webpack構(gòu)建時(shí)生成一個(gè)唯一的hash值
chunkhash 根據(jù)chunk生成hash值,來源于同一個(gè)chunk宙枷,則hash值就一樣
contenthash 根據(jù)內(nèi)容生成hash值掉房,文件內(nèi)容相同hash值就相同

3.3.1 hash

variableHash.jpg
function createHash(){
   return  require('crypto').createHash('md5');
}
let entry = {
    entry1:'entry1',
    entry2:'entry2'
}
let entry1 = 'require depModule1';//模塊entry1
let entry2 = 'require depModule2';//模塊entry2

let depModule1 = 'depModule1';//模塊depModule1
let depModule2 = 'depModule2';//模塊depModule2
//如果都使用hash的話,因?yàn)檫@是工程級別的慰丛,即每次修改任何一個(gè)文件卓囚,所有文件名的hash至都將改變。所以一旦修改了任何一個(gè)文件诅病,整個(gè)項(xiàng)目的文件緩存都將失效
let hash =  createHash()
.update(entry1)
.update(entry2)
.update(depModule1)
.update(depModule2)
.digest('hex');
console.log('hash',hash)
//chunkhash根據(jù)不同的入口文件(Entry)進(jìn)行依賴文件解析捍岳、構(gòu)建對應(yīng)的chunk,生成對應(yīng)的哈希值睬隶。
//在生產(chǎn)環(huán)境里把一些公共庫和程序入口文件區(qū)分開锣夹,單獨(dú)打包構(gòu)建,接著我們采用chunkhash的方式生成哈希值苏潜,那么只要我們不改動(dòng)公共庫的代碼银萍,就可以保證其哈希值不會(huì)受影響
let entry1ChunkHash = createHash()
.update(entry1)
.update(depModule1).digest('hex');;
console.log('entry1ChunkHash',entry1ChunkHash);

let entry2ChunkHash = createHash()
.update(entry2)
.update(depModule2).digest('hex');;
console.log('entry2ChunkHash',entry2ChunkHash);

let entry1File = entry1+depModule1;
let entry1ContentHash = createHash()
.update(entry1File).digest('hex');;
console.log('entry1ContentHash',entry1ContentHash);

let entry2File = entry2+depModule2;
let entry2ContentHash = createHash()
.update(entry2File).digest('hex');;
console.log('entry2ContentHash',entry2ContentHash);

3.3.2 hash

  • Hash 是整個(gè)項(xiàng)目的hash值,其根據(jù)每次編譯內(nèi)容計(jì)算得到恤左,每次編譯之后都會(huì)生成新的hash,即修改任何文件都會(huì)導(dǎo)致所有文件的hash發(fā)生改變

module.exports = {
+  entry: {
+    main: './src/index.js',
+    vender:['lodash']
+  },
  output:{
     path:path.resolve(__dirname,'dist'),
+    filename:'[name].[hash].js'
  },
  plugins: [
    new MiniCssExtractPlugin({
+      filename: "css/[name].[hash].css"
    })
  ]
};

3.3.2 chunkhash

  • chunkhash 采用hash計(jì)算的話贴唇,每一次構(gòu)建后生成的哈希值都不一樣,即使文件內(nèi)容壓根沒有改變飞袋。這樣子是沒辦法實(shí)現(xiàn)緩存效果戳气,我們需要換另一種哈希值計(jì)算方式,即chunkhash
  • chunkhash和hash不一樣巧鸭,它根據(jù)不同的入口文件(Entry)進(jìn)行依賴文件解析瓶您、構(gòu)建對應(yīng)的chunk,生成對應(yīng)的哈希值。我們在生產(chǎn)環(huán)境里把一些公共庫和程序入口文件區(qū)分開呀袱,單獨(dú)打包構(gòu)建贸毕,接著我們采用chunkhash的方式生成哈希值,那么只要我們不改動(dòng)公共庫的代碼夜赵,就可以保證其哈希值不會(huì)受影響

module.exports = {
  entry: {
    main: './src/index.js',
    vender:['lodash']
  },
  output:{
    path:path.resolve(__dirname,'dist'),
+   filename:'[name].[chunkhash].js'
  },
  plugins: [
    new MiniCssExtractPlugin({
+      filename: "css/[name].[chunkhash].css"
    })
  ]
};

3.3.3 contenthash

  • 使用chunkhash存在一個(gè)問題明棍,就是當(dāng)在一個(gè)JS文件中引入CSS文件,編譯后它們的hash是相同的寇僧,而且只要js文件發(fā)生改變 摊腋,關(guān)聯(lián)的css文件hash也會(huì)改變,這個(gè)時(shí)候可以使用mini-css-extract-plugin里的contenthash值,保證即使css文件所處的模塊里就算其他文件內(nèi)容改變嘁傀,只要css文件內(nèi)容不變歌豺,那么不會(huì)重復(fù)構(gòu)建
module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
+      filename: "css/[name].[contenthash].css"
    })
  ],
};

3.4 壓縮JS、CSS和HTML

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
+const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
+  mode: 'none',
  devtool: false,
  entry: './src/index.js',
+  optimization: {
+    minimize: true,
+    minimizer: [
+      new TerserPlugin(),
+    ],
+  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
    publicPath: '/',
  },
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    compress: true,
    port: 8080,
    open: true,
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: 'eslint-loader',
        enforce: 'pre',
        options: { fix: true },
        exclude: /node_modules/,
      },
      {
        test: /\.jsx?$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [[
              '@babel/preset-env',
              {
                useBuiltIns: 'usage'
                corejs: {
                  version: 3
                },
                targets: {
                  chrome: '60',
                  firefox: '60',
                  ie: '9',
                  safari: '10',
                  edge: '17',
                },
              },
            ], '@babel/preset-react'],
            plugins: [
              ['@babel/plugin-proposal-decorators', { legacy: true }],
              ['@babel/plugin-proposal-class-properties', { loose: true }],
            ],
          },
        },
        include: path.join(__dirname, 'src'),
        exclude: /node_modules/,
      },
      { test: /\.txt$/, use: 'raw-loader' },
      { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'] },
      { test: /\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader'] },
      { test: /\.scss$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader'] },
      {
        test: /\.(jpg|png|bmp|gif|svg)$/,
        use: [{
          loader: 'url-loader',
          options: {
            esModule: false,
            name: '[hash:10].[ext]',
            limit: 8 * 1024,
            outputPath: 'images',
            publicPath: '/images',
          },
        }],
      },
      {
        test: /\.html$/,
        loader: 'html-loader',
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
    template: './src/index.html',
+     minify: {  
+        collapseWhitespace: true,
+        removeComments: true
      }
    }),
    new MiniCssExtractPlugin({
      filename: 'css/[name].css',
    }),
+    new OptimizeCssAssetsWebpackPlugin(),
  ],
};

3.5 圖片壓縮

npm install image-webpack-loader --save-dev

 {
          test: /\.(png|svg|jpg|gif|jpeg|ico)$/,
          use: [
            'url-loader',
+           {
+             loader: 'image-webpack-loader',
+             options: {
+               mozjpeg: {
+                 progressive: true,
+                 quality: 65
+               },
+               optipng: {
+                 enabled: false,
+               },
+               pngquant: {
+                 quality: '65-90',
+                 speed: 4
+               },
+               gifsicle: {
+                 interlaced: false,
+               },
+               webp: {
+                 quality: 75
+               }
+             }
+           }
          ]
        }

3.6 px 自動(dòng)轉(zhuǎn)成rem

  • lib-flexible + rem心包,實(shí)現(xiàn)移動(dòng)端自適應(yīng)
  • px2rem-loader自動(dòng)將px轉(zhuǎn)換為rem
  • px2rem
  • 頁面渲染時(shí)計(jì)算根元素的font-size

3.6.1 安裝

cnpm i px2rem-loader lib-flexible -D

3.6.2 index.html

index.html

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>主頁</title>
    <script>
      let docEle = document.documentElement;
      function setRemUnit () {
        //750/10=75   375/10=37.5
        docEle.style.fontSize = docEle.clientWidth / 10 + 'px';
      }
      setRemUnit();
      window.addEventListener('resize', setRemUnit);
    </script>
</head>
<body>
    <div id="root"></div>
</body>

3.6.3 reset.css

src/reset.css

*{
    padding: 0;
    margin: 0;
}
#root{
    width:750px;
    height:750px;
    border:1px solid red;
    box-sizing: border-box;
}

3.6.4 webpack.config.js

 {
      test:/\.css$/,
        use:[{
                MiniCssExtractPlugin.loader,
                'css-loader',
                'postcss-loader',
                {
+                    loader:'px2rem-loader',
+                    options:{
+                        remUnit:75,
+                        remPrecesion:8
+                    }
+                }]
+            },

4. polyfill

  • @babel/preset-env會(huì)根據(jù)預(yù)設(shè)的瀏覽器兼容列表從stage-4選取必須的plugin,也就是說馒铃,不引入別的stage-x蟹腾,@babel/preset-env將只支持到stage-4

  • 三個(gè)概念

    • 最新ES 語法:比如,箭頭函數(shù)

    • 最新ES API:区宇,比如娃殖,Promise

    • 最新ES 實(shí)例方法:比如,String.prototype.includes

      4.1 babel-polyfill

  • Babel默認(rèn)只轉(zhuǎn)換新的javascript語法议谷,而不轉(zhuǎn)換新的API炉爆,比如 Iterator, Generator, Set, Maps, Proxy, Reflect,Symbol,Promise 等全局對象。以及一些在全局對象上的方法(比如 Object.assign)都不會(huì)轉(zhuǎn)碼卧晓。

  • 比如說芬首,ES6在Array對象上新增了Array.form方法,Babel就不會(huì)轉(zhuǎn)碼這個(gè)方法逼裆,如果想讓這個(gè)方法運(yùn)行郁稍,必須使用 babel-polyfill來轉(zhuǎn)換等

  • babel-polyfill 它是通過向全局對象和內(nèi)置對象的prototype上添加方法來實(shí)現(xiàn)的。比如運(yùn)行環(huán)境中不支持Array.prototype.find方法胜宇,引入polyfill, 我們就可以使用es6方法來編寫了耀怜,但是缺點(diǎn)就是會(huì)造成全局空間污染

  • @babel/@babel/preset-env為每一個(gè)環(huán)境的預(yù)設(shè)

  • @babel/preset-env默認(rèn)支持語法轉(zhuǎn)化,需要開啟useBuiltIns配置才能轉(zhuǎn)化API和實(shí)例方法

  • useBuiltIns可選值包括:"usage" | "entry" | false, 默認(rèn)為 false桐愉,表示不對 polyfills 處理财破,這個(gè)配置是引入 polyfills 的關(guān)鍵

4.1.1 安裝

npm i @babel/polyfill

4.1.2 "useBuiltIns": false

  • "useBuiltIns": false 此時(shí)不對 polyfill 做操作。如果引入 @babel/polyfill从诲,則無視配置的瀏覽器兼容左痢,引入所有的 polyfill
  • 86.4 KiB
import '@babel/polyfill';

 {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: [["@babel/preset-env", {
+                           useBuiltIns: false,
                        }], "@babel/preset-react"],
                        plugins: [
                            ["@babel/plugin-proposal-decorators", { legacy: true }],
                            ["@babel/plugin-proposal-class-properties", { loose: true }]
                        ]
                    }

                }
},

4.1.3 "useBuiltIns": "entry"

  • 在項(xiàng)目入口引入一次(多次引入會(huì)報(bào)錯(cuò))
  • "useBuiltIns": "entry" 根據(jù)配置的瀏覽器兼容,引入瀏覽器不兼容的 polyfill。需要在入口文件手動(dòng)添加 import '@babel/polyfill'抖锥,會(huì)自動(dòng)根據(jù) browserslist 替換成瀏覽器不兼容的所有 polyfill
  • 這里需要指定 core-js 的版本, 如果 "corejs": 3, 則 import '@babel/polyfill' 需要改成 import 'core-js/stable';import 'regenerator-runtime/runtime';
    • corejs默認(rèn)是2,配置2的話需要單獨(dú)安裝core-js@3
  • 80.6 KiB
  • 10.7 KiB
import '@babel/polyfill';

npm i core-js@3

import 'core-js/stable';
import 'regenerator-runtime/runtime';

 {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: [["@babel/preset-env", {
+                           useBuiltIns: 'entry',
+                           corejs: { version: 2 }
                        }], "@babel/preset-react"],
                        plugins: [
                            ["@babel/plugin-proposal-decorators", { legacy: true }],
                            ["@babel/plugin-proposal-class-properties", { loose: true }]
                        ]
                    }

                }
},

{
    "browserslist": {
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "production": [
      ">1%"
    ]
  },
}

4.1.4 "useBuiltIns": "usage"

  • "useBuiltIns": "usage" usage 會(huì)根據(jù)配置的瀏覽器兼容亿眠,以及你代碼中用到的 API 來進(jìn)行 polyfill,實(shí)現(xiàn)了按需添加
  • 當(dāng)設(shè)置為usage時(shí)磅废,polyfills會(huì)自動(dòng)按需添加纳像,不再需要手工引入@babel/polyfill
  • usage 的行為類似 babel-transform-runtime,不會(huì)造成全局污染,因此也會(huì)不會(huì)對類似 Array.prototype.includes() 進(jìn)行 polyfill
  • 0 bytes
  • 8.98 KiB
import '@babel/polyfill';
console.log(Array.from([]));

 {
    test: /\.jsx?$/,
    exclude: /node_modules/,
    use: {
        loader: 'babel-loader',
        options: {
            presets: [["@babel/preset-env", {
+               useBuiltIns: 'usage',
+               corejs: { version: 3 }
            }], "@babel/preset-react"],
            plugins: [
                ["@babel/plugin-proposal-decorators", { legacy: true }],
                ["@babel/plugin-proposal-class-properties", { loose: true }]
            ]
        }
    }
},

4.2 babel-runtime

  • Babel為了解決全局空間污染的問題拯勉,提供了單獨(dú)的包babel-runtime用以提供編譯模塊的工具函數(shù)
  • 簡單說 babel-runtime 更像是一種按需加載的實(shí)現(xiàn)竟趾,比如你哪里需要使用 Promise,只要在這個(gè)文件頭部import Promise from 'babel-runtime/core-js/promise'就行了
npm i babel-runtime -D

import Promise from 'babel-runtime/core-js/promise';
const p = new Promise(()=> {

});
console.log(p);

4.3 babel-plugin-transform-runtime

  • @babel/plugin-transform-runtime插件是為了解決
    • 多個(gè)文件重復(fù)引用相同helpers(幫助函數(shù))-> 提取運(yùn)行時(shí)
    • 新API方法全局污染 -> 局部引入
  • 啟用插件babel-plugin-transform-runtime后宫峦,Babel就會(huì)使用babel-runtime下的工具函數(shù)
  • babel-plugin-transform-runtime插件能夠?qū)⑦@些工具函數(shù)的代碼轉(zhuǎn)換成require語句岔帽,指向?yàn)閷?code>babel-runtime的引用
  • babel-plugin-transform-runtime 就是可以在我們使用新 API 時(shí)自動(dòng) import babel-runtime 里面的 polyfill
    • 當(dāng)我們使用 async/await 時(shí),自動(dòng)引入 babel-runtime/regenerator
    • 當(dāng)我們使用 ES6 的靜態(tài)事件或內(nèi)置對象時(shí)导绷,自動(dòng)引入 babel-runtime/core-js
    • 移除內(nèi)聯(lián)babel helpers并替換使用babel-runtime/helpers 來替換
  • corejs默認(rèn)是3,配置2的話需要單獨(dú)安裝@babel/runtime-corejs2
npm i @babel/runtime-corejs2 -D

      {
        test: /\.jsx?$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ["@babel/preset-env",'@babel/preset-react'],
            plugins: [
+              [
+                "@babel/plugin-transform-runtime",
+                {
+                  corejs: 2,//當(dāng)我們使用 ES6 的靜態(tài)事件或內(nèi)置對象時(shí)自動(dòng)引入 babel-runtime/core-js
+                  helpers: true,//移除內(nèi)聯(lián)babel helpers并替換使用babel-runtime/helpers 來替換
+                  regenerator: true,//是否開啟generator函數(shù)轉(zhuǎn)換成使用regenerator runtime來避免污染全局域
+                },
+              ],
              ['@babel/plugin-proposal-decorators', { legacy: true }],
              ['@babel/plugin-proposal-class-properties', { loose: true }],
            ],
          },
        },
      },

corejs: 2 corejs 2=>false 131 KiB => 224 bytes

const p = new Promise(()=> {});
console.log(p);

helpers true=>false 160 KiB=>150 KiB

class A {

}
class B extends A {

}
console.log(new B());

regenerator false=>true B490 bytes->28.6 Ki

function* gen() {

}
console.log(gen());

4.4 最佳實(shí)踐

  • babel-runtime適合在組件和類庫項(xiàng)目中使用犀勒,而babel-polyfill適合在業(yè)務(wù)項(xiàng)目中使用。

4.5 polyfill-service

  • polyfill.io自動(dòng)化的 JavaScript Polyfill 服務(wù)
  • polyfill.io通過分析請求頭信息中的 UserAgent 實(shí)現(xiàn)自動(dòng)加載瀏覽器所需的 polyfills
<script src="https://polyfill.io/v3/polyfill.min.js"></script>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末妥曲,一起剝皮案震驚了整個(gè)濱河市贾费,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌檐盟,老刑警劉巖褂萧,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異葵萎,居然都是意外死亡导犹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門羡忘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谎痢,“玉大人,你說我怎么就攤上這事卷雕〔暗茫” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵爽蝴,是天一觀的道長沐批。 經(jīng)常有香客問我,道長蝎亚,這世上最難降的妖魔是什么九孩? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮发框,結(jié)果婚禮上躺彬,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好宪拥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布仿野。 她就那樣靜靜地躺著,像睡著了一般她君。 火紅的嫁衣襯著肌膚如雪脚作。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天缔刹,我揣著相機(jī)與錄音球涛,去河邊找鬼。 笑死校镐,一個(gè)胖子當(dāng)著我的面吹牛亿扁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鸟廓,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼从祝,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了引谜?” 一聲冷哼從身側(cè)響起牍陌,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎煌张,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體退客,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡骏融,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了萌狂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片档玻。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖茫藏,靈堂內(nèi)的尸體忽然破棺而出境钟,到底是詐尸還是另有隱情扎瓶,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站浆熔,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蝶桶。R本人自食惡果不足惜耍贾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挟伙。 院中可真熱鬧楼雹,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至谴供,卻和暖如春块茁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背憔鬼。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工龟劲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人轴或。 一個(gè)月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓昌跌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親照雁。 傳聞我的和親對象是個(gè)殘疾皇子蚕愤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容