從基礎到實戰 手把手帶你掌握新版Webpack4.0

Webpack

本篇博客由慕課網視頻[從基礎到實戰手把手帶你掌握新版Webpack4.0](https://coding.imooc.com/class/316.html)閱讀整理而來,觀看視頻請支持正版。
本篇博客 Webpack 版本是4.0+,請確保你安裝了Node.js最新版本。

Webpack是什麼?

核心定義

webpack的核心定義是一個模塊打包工具。javascript

官方文檔查閱

https://webpack.js.org/concepts/css

GUIDES: 解決某一個方向問題答案,如代碼分割,TypeScripthtml

CONCEPTS: 一些核心的概念vue

CONFIGURATION: 某個配置項java

API: 寫一些loader,plugin插件node

LOADERS: 查看loader的做用,配置。(若是找不到到插件的githup上找)react

PLUGINS: 插件的做用,配置。(若是找不到到插件的githup上找)jquery

搭建Webpack環境

首先要安裝node.js以及npm, 並保證是最新版本。(最新版本會加快打包構建速度)webpack

安裝指令

全局安裝ios

npm install webpack webpack-cli -g

項目安裝

npm install webpack webpack-cli -D

帶有版本的安裝

npm install webpack@4.16.5 webpack-cli -D

卸載指令

npm uninstall webpack webpack-cli -g

查看當前Webpack版本

全局安裝查看指令:

webpack -v

項目內安裝查看指令:

npx webpack  -v

查看歷史Webpack版本

npm info webpack

使用Webpack配置文件

建立配置文件

在根目錄建立webpack.config.js

const path = require('path');//引入node核心模塊

module.exports = {
    //mode: 'production',//默認模式,會壓縮代碼,不寫便是默認,不寫會有提示
    mode: 'development',//開發模式, 不會壓縮代碼
    entry: {
       main: './src/index.js'
    },//從哪個文件開始打包
    output: {//輸出到哪裏
        filename: 'bundle.js',//輸出的文件名稱
        path: path.resolve(__dirname,'bundle') //輸出到哪個文件夾下, path後面跟絕對路徑
        //__dirname指的是webpack.config.js文件當前所在的路徑
    }
}

修改默認配置文件名字

把默認的webpack.config.js, 修改成webpackconfig.js

npx webpack --config webpackconfig.js

npx會在目錄下的node_modules下面找webpack,沒有就安裝一個。npm 則先去全局,而後再去當前目錄下的node_modules找webpack,沒有就不找了

webpack執行命令

  • 全局(global)
webpack index.js
  • 局部(local)
npx webpack index.js
  • 修改後腳本執行命令(npm scripts
npm run bundle

package.json文件

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "bundle":"webpack"
  },
  "author": "LiuJunFeng",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10"
  }
}

loader

loader是一個打包方案,它知道對於某一個特定的文件,webpack該如何進行打包。自己webpack是不知道對於一些文件(jpg,txt,excel)該如何處理的,可是loader知道。 因此webpack去求助loader就能夠啦。

打包靜態資源(圖片篇)

安裝插件

打包圖片資源能夠選用兩個loader, 一個是file-loader,一個是url-loader

npm i file-loader -D
npm i url-loader -D

url-loader更加友好, 它能夠經過圖片大小來判斷是使用base64格式圖片仍是直接打包成一個圖片資源文件。

配置文件

webpack.config.js

module: {
        rules: [{
            test: /\.(jpg|png|gif)$/,
            use: {
                // loader: 'file-loader',// 遇到jpg格式不知道怎麼打包就去求助file-loader插件  
                loader: 'url-loader',//圖片轉化爲base64, 不是單獨生成一個文件。 
                options: {
                    // placeholder 佔位符
                    name: '[name]_[hash].[ext]',//name 打包文件名字   name/原有文件名字  hash/本次打包哈希值  ext/原有名字後綴
                    outputPath: 'images/',//把圖片文件打包到images目錄下
                    limit: 204800//若是文件超過204800字節,就會像file-loader打包到dist目錄下生成一個文件, 
                    //若是文件小於204800字節,那就回變成base64字符串, 放到js內
                }
            }
        }]
    }

原理

File-loader底層處理邏輯,先將文件轉移到打包目錄下,再將dist中的文件路徑返回給index.js。

任何的靜態文件均可以使用file-loader插件, 只要你但願把靜態文件移動到打包目錄下而且獲取到此文件地址。

打包靜態資源(樣式篇)

安裝插件

css文件打包

打包css文件須要使用兩個loaderstyle-loadercss-loader

npm i style-loader css-loader -D

css-loader幫咱們分析出幾個css文件的引入關係, 最終將這些css文件合併成一段css。

style-loader 再獲得css-loader生成的內容後, 會把這段代碼掛載到html的head部分。

在打包css文件時, 必定要css-loaderstyle-loader配合使用。

sass文件打包
npm install sass-loader node-sass --save-dev
自動加廠商前綴
npm i -D postcss-loader
npm i autoprefixer -D

loader打包的執行順序是從下到上(從右到左)來執行, 以下:sass文件會先執行sass-loader, 處理完後再執行style-loader掛載到html的head部分。

配置

webpack.config.js

在module對象內的rule數組內添加如下代碼:

css樣式文件配置

{
            test: /\.scss$/,
            use:[
                'style-loader',
                {
                  loader: 'css-loader',
                  options: {
                        importLoaders: 2,//在scss又引入另一個scss時,有可能直接走css-loader,不走sass-loader和postcss-loader,加上此配置項可讓它繼續走下面兩個loader
                        //modules: true//開啓css模塊化打包  解決全局樣式衝突
                    }
                },
                'sass-loader',//sass文件編譯
                'postcss-loader'//加廠商前綴
            ]
        }]

字體文件配置

{
            test: /\.(eot|ttf|woff|woff2|svg)$/,
            use: {
                loader: 'file-loader',
                options:{
                    outputPath: 'fonts/',
                }
                
            }
        }

若是須要加廠商前綴, 須要在根目錄在建立一個文件, 取名爲postcss.config.js, 如下爲具體配置:

module.exports = {
    plugins: [
        require('autoprefixer')
    ]
}

默認打包支持的瀏覽器,不須要廠商前綴,能夠把瀏覽器條件放寬:

能夠在根目錄package.json文件中添加瀏覽器條件:

"browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]

使用plugins

plugin 能夠在webpack運行到某個時刻的時候, 幫你作一些事情。 很像vue中的生命週期函數。

生成html文件

安裝

npm i -D html-webpack-plugin

html-webpack-plugin會在打包結束的時刻, 自動生成一個html文件, 並把打包生成的js自動注入到這個html文件中。

配置

首先須要引入該插件, 而後再module對象內寫入plugins屬性名, 屬性是一個數組,數組內實例化該插件。

const HtmlWebpackPlugin = require('html-webpack-plugin');

plugins:[new HtmlWebpackPlugin()],

打包時清空dist目錄

安裝

npm i clean-webpack-plugin -D

clean-webpack-plugin 插件會在打包流程執行前清空dist目錄

配置

const {CleanWebpackPlugin} = require('clean-webpack-plugin');
plugins:[new HtmlWebpackPlugin({
        template: 'src/index.html'
    }),new CleanWebpackPlugin()],

基礎配置

Entry

entry: {
       main: './src/index.js',
       sub:'./src/index.js'
    },//從哪個文件開始打包

若是一個文件打包兩次, 能夠用以上方式配置, 一個文件會成爲main.js, 一個會成爲sub.js。

Output

output: {//輸出到哪裏
        publicPath:'http://cdn.com.cn',//文件使用cdn域名
        filename: '[name].js',//輸出的文件名稱  name對應的是entry裏的key值
		chunkFilename:'[name].chunk.js',//間接引入的文件會加上.chunk
        path: path.resolve(__dirname,'dist') //輸出到哪個文件夾下, path後面跟絕對路徑
        //__dirname指的是webpack.config.js文件當前所在的路徑
    }

SourceMap

可使用這個配置查看源代碼那裏有錯誤, 而不是打包後的文件錯誤。

好比你的js文件裏面寫的有問題,好比dist目錄下的main.js文件第96行出錯。SourceMap他是一個映射關係, 他知道dist目錄下的main.js文件96行實際上對應的是src目錄下index.js文件中的第一行。他就知道是index.js第一行出錯了。

配置

module.exports = {
    //mode: 'production',//默認模式,會壓縮代碼,不寫便是默認,不寫會有提示
    mode: 'development',//開發模式, 不會壓縮代碼
    devtool:'source-map',
    entry: {
       main: './src/index.js'
    },//從哪個文件開始打包

devtool:'inline-source-map'

source-map會在dist目錄下生成一個main.js.map, 而使用 inline-source-map會直接經過data-url的方式直接寫在main.js內的底部。

devtool:'cheap-inline-source-map'

當咱們遇到代碼量很大的時候, 若是咱們的代碼出了錯誤, 加上cheap它就會只告訴咱們是哪一行出了錯誤, 而不會告訴咱們第幾列。 打包會更加節省性能。

devtool:'cheap-module-inline-source-map'

source-map只會告訴咱們業務相關的代碼是否有錯, 而不會告訴咱們引入第三方模塊代碼是否有錯誤, 好比 loader內的錯誤。若是你想讓它也管第三方模塊的錯誤能夠加上module。

devtool:'eval',

使用eval配置執行效率最快, 性能最好的打包方式。它使用了eval的語法執行源代碼。可是若是比較複雜的代碼狀況下, 它提示出來的內容可能不太準確。

最佳實踐

若是你在開發環境中, 使用source-map建議使用devtool:'cheap-module-inline-eval-source-map', 它提示出來的錯誤是比較全的, 同時它的打包速度也是比較快的。

若是在線上環境中, 不必使用source-map做爲映射, 直接刪除此配置便可。
固然若是你也須要看錯誤提示, 可使用devtool:'cheap-module-source-map'。它的提示會更好一些。

WebpackDevServer

修改源碼自動就會進行打包。提高開發效率。

總共有如下兩種方式:

webpack --watch

監聽文件改動實時進行打包

package.json

"scripts": {
    "watch": "webpack --watch"
  },

devServer

自動打包並刷新瀏覽器,還能夠模擬服務器上的特性。 vue以及react腳手架使用的都是此配置。推薦使用, 這個也是業界最常用的方案。

安裝插件

npm i webpack-dev-server -D

配置

webpack.config.js

devServer: {
        contentBase: './dist',//服務器起在哪個文件夾下
        open: true,//自動打開瀏覽器
        port: 8080,//使用哪一個端口號
        proxy: {
            './api':'http://locallhost:3000'
        }//若是訪問api這個地址,也就是locallhost:8000/api, 它會幫你轉發到http://locallhost:3000這個地址上
    },

package.json

"scripts": {
    "watch": "webpack --watch",
    "start": "webpack-dev-server"
  },

熱模塊更新(HMR)

HMR是Hot Module Replacement的縮寫。

它能夠只更新你改動的文件, 不會直接刷新瀏覽器。

優勢:

  1. 很是方便調試樣式。

配置

webpack.config.js

const webpack = require('webpack');
devServer: {
        contentBase: './dist',//服務器起在哪個文件夾下
        open: true,//自動打開瀏覽器
        port: 8080,//使用哪一個端口號
        // proxy: {
        //     './api':'http://locallhost:3000'
        // }//若是訪問api這個地址,也就是locallhost:8000/api, 它會幫你轉發到http://locallhost:3000這個地址上
        hot:true,//開啓熱更新
        hotOnly:true//即使熱更新沒有生效,也不刷新瀏覽器
    },
plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html"
    }),
    new CleanWebpackPlugin(),
    new webpack.HotModuleReplacementPlugin()
  ],

index.js內

enter description here

若是開啓熱更新, number.js文件只要發生變化就會從新執行一下

使用Babel處理ES6語法

業務代碼

安裝插件

npm i -D babel-loader @babel/core

npm i @babel/preset-env -D
 
npm i --save @babel/polyfill

babel-loader插件只是做爲babel與webpack溝通的橋樑, 若是想要翻譯ES6語法, 須要安裝@babel/preset-env插件。

babel/polyfill 用來補充babel/preset-env的, 有的語法babel/preset-env不能翻譯(如Promise), 這時候可使用babel/polyfill。

配置

在業務代碼js的頂部引入babel/polyfill 。若是使用了useBuiltIns:"usage",也能夠不引入此插件。

import "@babel/polyfill";

webpack.config.js

{
        test: /\.js$/,//js文件由ES6轉成ES5
        exclude: /node_modules/,//無論這個文件夾下的js文件
        loader: "babel-loader",
        options: {
            presets: [["@babel/preset-env",{
              targets: {
                edge: "17",
                firefox: "60",
                chrome: "67",
                safari: "11.1"
              },//瀏覽器版本,如chrome版本大於67將不會翻譯成ES5
              useBuiltIns:"usage"//js裏用哪一個翻譯那個,不用的語法特性不會翻譯, 減小文件體積
            }]]
        }
      }

打包組件庫

若是開發組件庫或者第三方模塊的時候, 不要使用@babel/polyfill插件。由於它在注入promise或者map方法的時候, 它會經過全局變量的方式注入, 會污染到全局環境。

安裝插件

咱們能夠換一種方式:

首先把業務代碼中引入的@babel/polyfill註釋掉, 而後按照 @babel/plugin-transform-runtime和@babel/runtime插件。

npm i -D @babel/plugin-transform-runtime
 npm i --save @babel/runtime
  npm i --save @babel/runtime-corejs2

配置

webpack.config.js

{
        test: /\.js$/,//js文件由ES6轉成ES5
        exclude: /node_modules/,//無論這個文件夾下的js文件
        loader: "babel-loader",
        options: {
            plugins: [["@babel/plugin-transform-runtime",{
              "corejs":2,
              "helpers":true,
              "regenerator":true,
              "useESModules":false
            }]]
        }
      }

若是plugins內corejs配置了2, 那麼就要安裝@babel/runtime-corejs2這個插件了。

固然, 在咱們配置過多的babel配置時, 也能夠在根目錄建立一個.babelrc文件。用來放置相關配置, 以下:

webpack.config.js

{
        test: /\.js$/,//js文件由ES6轉成ES5
        exclude: /node_modules/,//無論這個文件夾下的js文件
		//include: path.resolve(__dirname,'../src')//只對src目錄的js文件打包
        loader: "babel-loader"
      }

babelrc

{
    "plugins": [["@babel/plugin-transform-runtime",{
      "corejs":2,
      "helpers":true,
      "regenerator":true,
      "useESModules":false
    }]]
}

配置React代碼打包

安裝插件

npm i react react-dom --save
 npm i --save-dev @babel/preset-react

配置

babelrl

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "edge": "17",
          "firefox": "60",
          "chrome": "67",
          "safari": "11.1"
        },
        "useBuiltIns": "usage"
      }
    ],
    "@babel/preset-react"
  ]
}
這個執行順序是按照從下到上,從右到左來執行的。順序必定不要寫反了。它是先執行react轉成js, 而後執行babel轉成ES5的。

Tree Shaking

Tree Shaking支持ES6的Module引入方式,它只支持靜態引入的方式, 動態引入的方式它不支持。

Tree Shaking中文翻譯是搖樹的意思, 大意就是把無效的代碼搖晃掉, 只留下有用的代碼。
好比你引入了一個模塊中的方法, 它就只打包你引入的方法, 不須要的方法不會進行打包了。

在開發環境中, 是沒有Tree Shaking功能的, 若是須要請添加如下配置:

webpack.config.js

在線上環境中能夠不配置這個

optimization:{
    usedExports:true
  },
  output: {
    //輸出到哪裏
    filename: "[name].js", //輸出的文件名稱  name對應的是entry裏的key值
    path: path.resolve(__dirname, "dist") //輸出到哪個文件夾下, path後面跟絕對路徑
    //__dirname指的是webpack.config.js文件當前所在的路徑
  }

package.json文件內

"name": "webpack-demo",
  "sideEffects": ["@babel/polly-fill"],
  "version": "1.0.0",
  "description": "",
  "private": true,

配置"sideEffects": ["@babel/polly-fill"],後, Tree Shaking不會對這個插件有任何做用了。

固然沒有引用@babel/polly-fill也能夠設置爲false

若是你的業務js代碼引入了js,以下:

enter description here

你也須要在"sideEffects"進行配置, 通常咱們會對css文件進行如下配置:

"sideEffects": [
    "*.css"
  ]

只要遇到任何的css文件,那麼也不要去使用Tree Shaking。

Develoment 和 Production 模式的區分打包

在開發環境能夠方便咱們開發, 有熱模塊更新DevServer等配置能夠更加方便咱們的調試代碼。
而線上環境會壓縮代碼, 對source-map精簡(沒有報錯信息或者只顯示行錯誤)。

開發環境

首先咱們須要把webpack.config.js修改成webpack.dev.js, 表示開發環境。

const path = require("path"); //引入node核心模塊
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const webpack = require("webpack");

module.exports = {
  //mode: 'production',//默認模式,會壓縮代碼,不寫便是默認,不寫會有提示
  mode: "development", //開發模式, 不會壓縮代碼
  devtool: "cheap-module-eval-source-map",
  entry: {
    main: "./src/index.js"
  }, //從哪個文件開始打包
  devServer: {
    contentBase: "./dist", //服務器起在哪個文件夾下
    open: true, //自動打開瀏覽器
    port: 8080, //使用哪一個端口號
    // proxy: {
    //     './api':'http://locallhost:3000'
    // }//若是訪問api這個地址,也就是locallhost:8000/api, 它會幫你轉發到http://locallhost:3000這個地址上
    hot: true, //開啓熱更新
    hotOnly: true //即使熱更新沒有生效,也不刷新瀏覽器
  },
  module: {
    rules: [
      {
        test: /\.(jpg|png|gif)$/,
        use: {
          // loader: 'file-loader',// 遇到jpg格式不知道怎麼打包就去求助file-loader插件
          loader: "url-loader", //圖片轉化爲base64, 不是單獨生成一個文件。
          options: {
            // placeholder 佔位符
            name: "[name]_[hash].[ext]", //name 打包文件名字   name/原有文件名字  hash/本次打包哈希值  ext/原有名字後綴
            outputPath: "images/", //把圖片文件打包到images目錄下
            limit: 204800 //若是文件超過204800字節,就會像file-loader打包到dist目錄下生成一個文件,
            //若是文件小於204800字節,那就回變成base64字符串, 放到js內
          }
        }
      },
      {
        test: /\.(eot|ttf|woff|woff2|svg)$/,
        use: {
          loader: "file-loader",
          options: {
            outputPath: "fonts/"
          }
        }
      },
      {
        test: /\.scss$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 2 //在scss又引入另一個scss時,有可能直接走css-loader,不走sass-loader和postcss-loader,加上此配置項可讓它繼續走下面兩個loader
              //modules: true//開啓css模塊化打包  解決全局樣式衝突
            }
          },
          "sass-loader", //sass文件編譯
          "postcss-loader" //加廠商前綴
        ]
      },
      {
        test: /\.css$/,
        use: [
          "style-loader",
          "css-loader",
          "postcss-loader" //加廠商前綴
        ]
      },
      {
        test: /\.js$/,//js文件由ES6轉成ES5
        exclude: /node_modules/,//無論這個文件夾下的js文件
        loader: "babel-loader"
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html"
    }),
    new CleanWebpackPlugin(),
    new webpack.HotModuleReplacementPlugin()
  ],
  optimization:{
    usedExports:true
  },
  output: {
    //輸出到哪裏
    filename: "[name].js", //輸出的文件名稱  name對應的是entry裏的key值
    path: path.resolve(__dirname, "dist") //輸出到哪個文件夾下, path後面跟絕對路徑
    //__dirname指的是webpack.config.js文件當前所在的路徑
  }
};

啓動命令

package.json

"scripts": {
    "dev": "webpack-dev-server --config wepack.dev.js"
  },

在開發環境內也能夠把hotOnly: true去掉, 使瀏覽器能自動刷新。

線上環境

而後建立webpack.prod.js文件, 表示線上環境。

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const webpack = require("webpack");

module.exports = {
  mode: "production",
  devtool: "cheap-module-source-map",
  entry: {
    main: "./src/index.js"
  },
  module: {
    rules: [
      {
        test: /\.(jpg|png|gif)$/,
        use: {
          loader: "url-loader",
          options: {
            name: "[name]_[hash].[ext]",
            outputPath: "images/",
            limit: 204800
          }
        }
      },
      {
        test: /\.(eot|ttf|woff|woff2|svg)$/,
        use: {
          loader: "file-loader",
          options: {
            outputPath: "fonts/"
          }
        }
      },
      {
        test: /\.scss$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 2
            }
          },
          "sass-loader",
          "postcss-loader"
        ]
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader", "postcss-loader"]
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader"
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html"
    }),
    new CleanWebpackPlugin()
  ],
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "dist")
  }
};

啓動命令

package.json

"scripts": {
    "dev": "webpack-dev-server --config wepack.dev.js",
    "build":"webpack --config webpack.prod.js"
  },

打包完成後把dist文件夾丟給後端使用便可。

公共文件

咱們能夠發如今webpack.dev.js和webpack.prod.js中有不少相同代碼, 這時候咱們能夠把相同的代碼抽離出來放到webpack.common.js中。

咱們須要引入一個插件合併common文件與prod或者dev文件

npm i webpack-merge -D

建立webpack.common.js, 相同代碼放入此文件

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
  entry: {
    main: "./src/index.js"
  },
  module: {
    rules: [
      {
        test: /\.(jpg|png|gif)$/,
        use: {
          loader: "url-loader",
          options: {
            name: "[name]_[hash].[ext]",
            outputPath: "images/",
            limit: 204800
          }
        }
      },
      {
        test: /\.(eot|ttf|woff|woff2|svg)$/,
        use: {
          loader: "file-loader",
          options: {
            outputPath: "fonts/"
          }
        }
      },
      {
        test: /\.scss$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 2
            }
          },
          "sass-loader",
          "postcss-loader"
        ]
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader", "postcss-loader"]
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader"
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html"
    }),
    new CleanWebpackPlugin()
  ],
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "dist")
  }
};

webpack.dev.js

const webpack = require("webpack");
const merge = require("webpack-merge");
const commonConfig = require("./webpack.common.js");

const devConfig = (module.exports = {
  mode: "development",
  devtool: "cheap-module-eval-source-map",
  devServer: {
    contentBase: "./dist",
    open: true,
    port: 8080,
    hot: true,
    hotOnly: true
  },
  plugins: [new webpack.HotModuleReplacementPlugin()],
  optimization: {
    usedExports: true
  }
});

module.exports = merge(commonConfig, devConfig);

webpack.prod.js

const merge = require("webpack-merge");
const commonConfig = require("./webpack.common.js");

const prodConfig = {
  mode: "production",
  devtool: "cheap-module-source-map"
};

module.exports = merge(commonConfig, prodConfig);

固然, 若是咱們這幾個配置文件放在了根目錄的build文件夾內, 你須要在package.json內對指令進行更改,把目錄須要更改到build目錄下

package.json

"scripts": {
    "dev": "webpack-dev-server --config ./build/webpack.dev.js",
    "build": "webpack --config ./build/webpack.prod.js"
  },

固然, 輸出的目錄也須要修改一下

webpack.common.js

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

Webpack 和 Code Splitting

Code Splitting指的是代碼分割。若是咱們不使用代碼分割,打包出來的文件會很大, 加載時間會很長。還有一種狀況, 咱們引入一個lodash庫, 這部分代碼不變, 僅僅是業務代碼改變了, 若是咱們再次打包就會所有又加載一遍, 影響了加載的速度。 咱們但願的是lodash庫不須要再次加載。

咱們先安裝一個插件lodash

npm i lodash --save

src目錄建立一個lodash.js文件

import _ from 'lodash';
window._ = _;

webpack.common.js

entry: {
    lodash: "./src/lodash.js",
    main: "./src/index.js"
  },

自動進行代碼分割

同步代碼方式

同步代碼

以上爲同步引入方式,可按照一下代碼進行配置:

webpack.common.js

optimization: {
    splitChunks: {
      chunks: 'all'
    }
  },

這段代碼能幫你作代碼分割。

異步代碼

異步引入指的是如下狀況:

異步代碼

安裝插件
npm i babel-plugin-dynamic-import-webpack

.babelrc

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "edge": "17",
          "firefox": "60",
          "chrome": "67",
          "safari": "11.1"
        },
        "useBuiltIns": "usage"
      }
    ],
    "@babel/preset-react"
  ],
  "plugins": ["dynamic-import-webpack"]
}

小結

代碼分割,和webpack無關

webpack中實現代碼分割,兩種方式

  1. 同步代碼: 只須要在webpack.common.js中作optimization的配置便可
  2. 異步代碼(import): 異步代碼,無需作任何配置,會自動進行代碼分割,放置到新的文件中

SplitChunksPlugin 配置參數詳解

魔法註釋

魔法註釋

須要在package.json去掉babel-plugin-dynamic-import-webpack插件,由於它是一個第三方的插件,不支持魔法註釋, 咱們須要一個官方的插件來進行魔法註釋。

npm i -D @babel/plugin-syntax-dynamic-import

.babelrc

plugins修改成babel/plugin-syntax-dynamic-import

"plugins": ["@babel/plugin-syntax-dynamic-import"]

webpack.common.js

若是optimization不寫任何內容, 只是一個空對象, 會按照如下默認配置打包:

optimization: {
    splitChunks: {
      chunks: 'aysnc',
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name:true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: false
      }
    }
  }

通常按照這個選項配置便可:

webpack.common.js

optimization: {
    splitChunks: {
      chunks: 'all',
    }
  }

參數說明

chunks

chunks: 'async'

默認配置是async

只對異步引入進行代碼分割

異步引入

chunks: 'initial'

只對同步引入進行代碼分割

同步引入

chunks: 'all'

所有都會進行代碼分割,同時必需要配置defaultVendors

defaultVendors: {
 test: /[\\/]node_modules[\\/]/,
 priority: -10
}

執行過程是首先看是否須要代碼分割, 也就是chunks配置,若是須要分割會走到cacheGroups內看如何分割, 從defaultVendors看看是否在node_modules裏, 那它就符合這個配置的要求, 因而他就會把須要打包的模塊(如lodash)打包在一個Vendors組裏面去。

enter description here

這個文件是在vendors組內, 入口文件是main.js

vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          filename: 'vendors.js'
        },

加上filename可使文件名改成vendors.js

minSize

minSize: 30000

引入的文件大於30000字節才進行分割, 通常配置30000。

若是不是node_modules內的文件, 須要配置一個默認選項:

cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          filename: 'vendors.js'
        },
        default: {
          priority: -20,
          reuseExistingChunk: true,
          filename: 'common.js'
        }
      }

這樣打包後會在文件前加default~的前綴, 以下:

enter description here

maxSize

基本上沒用, 通常不會配置這個選項。

這個配置是進行二次拆分的選項。 好比一個lodash庫, 你配置50000, 也就是50kb, 這個lodash庫是1m, 那麼它會拆分紅20個文件, 可是通常狀況下lodash庫是拆分不了的。

minChunks

當一個模塊引入了多少次纔會進行代碼分割。

maxAsyncRequests

同時加載的模塊數量。通常不會配置。默認便可。

好比咱們這個選項配置爲5, 加入我引入了10個庫, 分割了10個文件,那你一打開網頁的時候, 同時要加載10個文件。那就違反了maxAsyncRequests配置爲5的要求, 同時只能加載5個請求, 那麼webpack在打包前5個庫的時候會爲你生成5個js文件, 超過5個它就不會作代碼分割了。

maxInitialRequests

通常不會配置。默認便可。

整個網站首頁加載的時候, 或者說入口文件加載的時候, 入口文件可能會引入其它的js文件。入口文件引入的庫若是是配置爲3, 那麼最多作3次代碼分割。 超過3個就不會再作代碼分割了。

automaticNameDelimiter

組和文件名作鏈接時的鏈接符

name

true會讓cacheGroups起的名字有效。也就是filename

cacheGroups

cacheGroups也就是緩存組, 與上面的選項息息相關。會把符合條件的代碼緩存到一個組內。

priority指的是優先級。 哪一個大優先哪一個。

reuseExistingChunk: true

假如我有一個a模塊, 又有一個b模塊。a模塊內又使用了b模塊。在打包a代碼的時候, 因爲a模塊使用了
b模塊,因此b模塊代碼也會被打包進去。可是若是配置這個選項,它會去看, 以前b模塊代碼已經被引入過,那麼它會去複用以前打包的模塊。

Chunks是什麼?

webpack打包過程當中生成的每一個文件都是一個chunk

意義

代碼分割配置

minChunks: 2 至少兩個打包文件引入這個模塊 才單獨分割打包

Lazy Loading 懶加載

如下代碼能夠實現懶加載, 在點擊頁面後再加載代碼

async function getComponent() {
	const { default: _ } = await import(/* webpackChunkName:"lodash" */ 'lodash');
	const element = document.createElement('div');
	element.innerHTML = _.join(['Dell', 'Lee'], '-');
	return element;
}

document.addEventListener('click', () =>{
	getComponent().then(element => {
		document.body.appendChild(element);
	});
})

優勢: 頁面加載速度更快。

懶加載並非webpack的功能, 它是ESModule的一個概念, 只不過webpack可以識別對它進行代碼分割。

打包分析

https://github.com/webpack/analyse

package.json

"scripts": {
    "dev-build": "webpack --profile --json > stats.json --config ./build/webpack.dev.js",
    "dev": "webpack-dev-server --config ./build/webpack.dev.js",
    "build": "webpack --config ./build/webpack.prod.js"
  },

整個打包過程的描述放在stats.json文件內。

打開http://webpack.github.io/analyse/,把文件上傳便可獲得如下的分析圖。

分析圖

固然也可使用Webpack Bundle Analyzer這個插件。

Preloading, Prefetching

webpack 指望首次加載速度最優化,不是利用緩存在下次加載時提升訪問速度 應該提升代碼使用率

show coverage 代碼使用率

交互代碼能夠放到單獨的異步模塊裏 提升加載速度及頁面利用率

以下:

業務代碼

異步代碼模塊

可是異步加載交互代碼時:例如當點擊的時候纔再加載異步代碼,雖然提升了頁面初始化速度,可是對用用戶點擊
的體驗很差,速度太慢;

爲了解決懶加載帶來的問題:使用prefretch preload

prefetch:會等主流程都加載完成,等待空閒再加載;(最優)

import(/* webpackPrefetch: true */ 'LoginModal');

preload:是和主線程一塊兒加載

css代碼分割

想要使用css代碼分割咱們必需要修改一下 Tree Shaking 配置

package.json

"sideEffects": [
    "*.css",
    "*.scss"
  ],

首先, 咱們須要安裝一個插件

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

引入css文件

import './style.css';

webpack.prod.js配置

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  plugins: [new MiniCssExtractPlugin({
        filename: "[name].css",
     	chunkFilename: "[name].chunk.css"
	})],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
};

壓縮css代碼:

npm i optimize-css-assets-webpack-plugin -D

webpack.prod.js

const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');

  optimization: {
    minimizer: [new OptimizeCSSAssetsPlugin({})]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[name].chunk.css"
    })
  ]
高級用法

多個入口文件引入的css文件打包到一塊兒, 須要藉助splitChunks,額外增長一個style組,只要發現你的打包文件是css文件, 統一打包到一個叫styles.css的文件內,enforcetrue忽略默認的一些參數(如minsize之類)。只要你是一個css文件我就作代碼的拆分,把代碼分割到style.css文件內。

webpack.prod.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        styles: {
          name: 'styles',
          test: /\.css$/,
          chunks: 'all',
          enforce: true,
        },
      },
    },
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
};

當咱們想要每一個入口文件打包到不一樣的css文件內的時候,仍是利用cacheGroups, 以下: 若是入口文件是foo文件就走fooStyles的邏輯,若是是bar文件就走barStyles的邏輯。

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

function recursiveIssuer(m) {
  if (m.issuer) {
    return recursiveIssuer(m.issuer);
  } else if (m.name) {
    return m.name;
  } else {
    return false;
  }
}

module.exports = {
  entry: {
    foo: path.resolve(__dirname, 'src/foo'),
    bar: path.resolve(__dirname, 'src/bar'),
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        fooStyles: {
          name: 'foo',
          test: (m, c, entry = 'foo') =>
            m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
          chunks: 'all',
          enforce: true,
        },
        barStyles: {
          name: 'bar',
          test: (m, c, entry = 'bar') =>
            m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
          chunks: 'all',
          enforce: true,
        },
      },
    },
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
};

Webpack 與瀏覽器緩存

webpack.common.js

關閉性能上的警告

performance: false,

在咱們使用webpack的時候,線上代碼修改的時候,由於代碼的名字沒有改變致使瀏覽器在加載
網頁的時候,取的是緩存中的代碼,致使沒有及時的獲取最新的代碼,這時候就要清除瀏覽器的緩存, 咱們能夠利用輸出文件配置contenthash, 這樣只有在修改代碼了纔會改變hash值, 就能夠作到修改了代碼線上瀏覽器緩存也會更新。

webpack.prod.js

output: {
 filename: '[name].[contenthash].js',
 chunkFilename: '[name].[contenthash].js'
 }

老版本若是發現即便沒修改代碼,打包文件的hash值也不同,請按下圖配置:

enter description here

Shimming 的做用

shimming做用:解決webpack打包的兼容性問題.

好比你引入一個jquery.ui的庫, 可是沒有引入jquery, 使用了$寫代碼。 可是在你的業務是有引入jquery的, 這樣在業務代碼若是運行jqueryui初始化會報錯的。 因此咱們應該使用shimming。

webpack.common.js

const webpack = require("webpack");

webpack.common.js

plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html"
    }),
    new CleanWebpackPlugin(),
    new webpack.ProvidePlugin({
      $: 'jquery',
	  _join:['lodash','join'],
	  //_: 'lodash'
    })
  ],

以上代碼的意思就是若是個人一個模塊中使用了$, 那我就會在模塊裏自動幫你引入jquery這個模塊。
使用_join就是lodash下的join方法

模塊內this指向window

每一個模塊的this都是指向自身模塊, 不會指向window。 若是想要指向window, 能夠引入這個插件:

npm i imports-loader --save-dev

webpack.common.js

{
        test: /\.js$/,
        exclude: /node_modules/,
        use: [{
          loader: "babel-loader",
        },{
          loader: "imports-loader?this=>window"
        }]
      }

環境變量

咱們換一種方式來啓動不一樣環境下的打包方式, 經過一個變量:

package.json

{
  "name": "webpack-demo",
  "sideEffects": [
    "*.css",
    "*.scss"
  ],
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "dev-build": "webpack --profile --json > stats.json --config ./build/webpack.common.js",
    "dev": "webpack-dev-server --config ./build/webpack.common.js",
    "build": "webpack --env.production --config ./build/webpack.common.js"
  },
  "author": "LiuJunFeng",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.8.4",
    "@babel/plugin-syntax-dynamic-import": "^7.8.3",
    "@babel/plugin-transform-runtime": "^7.8.3",
    "@babel/preset-env": "^7.8.4",
    "@babel/preset-react": "^7.8.3",
    "autoprefixer": "^9.7.4",
    "babel-loader": "^8.0.6",
    "clean-webpack-plugin": "^3.0.0",
    "css-loader": "^3.4.2",
    "file-loader": "^5.0.2",
    "html-webpack-plugin": "^3.2.0",
    "imports-loader": "^0.8.0",
    "mini-css-extract-plugin": "^0.9.0",
    "node-sass": "^4.13.1",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss-loader": "^3.0.0",
    "sass-loader": "^8.0.2",
    "style-loader": "^1.1.3",
    "url-loader": "^3.0.0",
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10",
    "webpack-dev-server": "^3.10.3",
    "webpack-merge": "^4.2.2"
  },
  "dependencies": {
    "@babel/polyfill": "^7.8.3",
    "@babel/runtime": "^7.8.4",
    "@babel/runtime-corejs2": "^7.8.4",
    "jquery": "^3.4.1",
    "lodash": "^4.17.15",
    "react": "^16.12.0",
    "react-dom": "^16.12.0"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}

webpack.common.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const webpack = require("webpack");
const merge = require("webpack-merge");
const devConfig = require("./webpack.dev.js");
const prodConfig = require("./webpack.prod.js")

const commonConfig = {
  entry: {
    main: "./src/index.js"
  },
  module: {
    rules: [
      {
        test: /\.(jpg|png|gif)$/,
        use: {
          loader: "url-loader",
          options: {
            name: "[name]_[hash].[ext]",
            outputPath: "images/",
            limit: 204800
          }
        }
      },
      {
        test: /\.(eot|ttf|woff|woff2|svg)$/,
        use: {
          loader: "file-loader",
          options: {
            outputPath: "fonts/"
          }
        }
      },
      {
        test: /\.scss$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 2
            }
          },
          "sass-loader",
          "postcss-loader"
        ]
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [{
          loader: "babel-loader",
        },{
          loader: "imports-loader?this=>window"
        }]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html"
    }),
    new CleanWebpackPlugin(),
    new webpack.ProvidePlugin({
      $: 'jquery'
    })
  ],
  optimization: {
    usedExports: true,
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          name: 'vendors'
        }
      }
    }
  },
  performance: false,
  output: {
    path: path.resolve(__dirname, "../dist")
  }
};


module.exports = (env) => {
  if(env && env.production) {
    return merge(commonConfig, prodConfig)
  } else {
    return merge(commonConfig, devConfig)
  }
}

webpack.dev.js

const webpack = require("webpack");

const devConfig = (module.exports = {
  mode: "development",
  devtool: "cheap-module-eval-source-map",
  devServer: {
    contentBase: "./dist",
    open: true,
    port: 8080,
    hot: true,
    hotOnly: true
  },
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 2
            }
          },
          "sass-loader",
          "postcss-loader"
        ]
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader", "postcss-loader"]
      }
    ]
  },
  plugins: [new webpack.HotModuleReplacementPlugin()],
  output: {
    filename: "[name].js",
    chunkFilename:'[name].js',
  }
});

module.exports =  devConfig;

webpack.prod.js

const webpack = require("webpack");

const devConfig = (module.exports = {
  mode: "development",
  devtool: "cheap-module-eval-source-map",
  devServer: {
    contentBase: "./dist",
    open: true,
    port: 8080,
    hot: true,
    hotOnly: true
  },
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 2
            }
          },
          "sass-loader",
          "postcss-loader"
        ]
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader", "postcss-loader"]
      }
    ]
  },
  plugins: [new webpack.HotModuleReplacementPlugin()],
  output: {
    filename: "[name].js",
    chunkFilename:'[name].js',
  }
});

module.exports =  devConfig;

咱們也能夠在package.json裏這樣寫:

enter description here

那麼對應的webpack.common.js是這樣的

enter description here

還能夠這麼寫:

package.json

enter description here

webpack.common.js

enter description here

打包組件庫

建立一個新的文件夾Library, 並初始化項目:

npm init -y

安裝webpack

npm i webpack webpack-cli --save

建立webpack.config.js配置如下代碼:

const path = require('path');
module.exports = {
    mode: 'production',
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname,'dist'),
        filename: 'library.js',
        libraryTarget: 'umd',//不管什麼方式引入組件均可以正確引入到
    }
}

package.json

{
  "name": "Library",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "LIU",
  "license": "MIT",
  "dependencies": {
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10"
  }
}

若是你想這樣經過src引入js, 而且想經過library獲取它下面的方法或屬性,

enter description here

添加library: 'library'便可

webpack.config.js

const path = require('path');
module.exports = {
    mode: 'production',
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname,'dist'),
        filename: 'library.js',
        library: 'library',
        libraryTarget: 'umd',//不管什麼方式引入組件均可以正確引入到
   		//libraryTarget: 'this',
		//libraryTarget: 'window',
		//libraryTarget: 'global',
   }
}

這樣配置好如下幾種方式均可以正確引入了:

enter description here

webpack.config.js

const path = require('path');
module.exports = {
    mode: 'production',
    entry: './src/index.js',
    externals: ["lodash"],//打包過程當中若是遇到lodash庫你就忽略這個庫,不要把它打包到你的代碼中去,防止使用時用戶重複引入庫(例如:lodash)
    // externals: {
    //     lodash: {
    //         //root: '_',//全局script標籤引入,必須在頁面注入一個名字叫_的變量
    //         //commonjs: 'lodash'//若是個人lodash在commonjs這個環境中使用,而且引入名字必須是lodash
    //     }
    // },
    output: {
        path: path.resolve(__dirname,'dist'),
        filename: 'library.js',
        library: 'library',
        libraryTarget: 'umd',//不管什麼方式引入組件均可以正確引入到
    }
}

commonjs

package.json

{
  "name": "Library",
  "version": "1.0.0",
  "description": "",
  "main": "./dist/library.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "LIU",
  "license": "MIT",
  "dependencies": {
    "lodash": "^4.17.15",
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10"
  }
}

發佈組件庫流程:

  • 登陸npm
  • 命令行npm adduser,輸入用戶名,密碼
  • 命令行 npm publish 發佈到npm

PWA 的打包配置

安裝插件模擬服務器

npm i http-server --save-dev

package.json

scripts命令

"start": "http-server dist",

PWA:是一種強緩存技術,訪問過的頁面就算服務器斷開,也能經過緩存瀏覽以前訪問的頁面

只在上線環境配置就能夠了, 開發環境不用考慮服務器掛掉不掛掉的問題。

安裝插件

npm i workbox-webpack-plugin --save-dev

配置

webpack.prod.js

const WorkboxPlugin = require('workbox-webpack-plugin');
plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[name].chunk.css"
    }),
    new WorkboxPlugin.GenerateSW({
      clientsClaim: true,
      skipWaiting: true
    })
  ],

業務邏輯文件增長如下代碼:

if ('serviceWorker' in navigator){
    window.addEventListener('load', ()=>{
        navigator.serviceWorker.register('/service-worker.js')
        .then(registration => {
            console.log('service-worker registed');
        }).catch(error => {
            console.log('service-worker register error');
        })
    })
}

TypeScript 的打包配置

安裝插件:

npm i ts-loader typescript --save

webpack-config.js

module: {
        rule: [{
            test: /\.tsx?$/,
            use: 'ts-loader'
        }]
    },

建立tsconfig.json文件

{
    "compilerOptions": {
        "outDir": "./dist",//輸出目錄
        "module": "es6",//只容許es6 Module的方式引入模塊
        "target":"es5",//編譯爲Es5這樣的代碼
        "allowJs":true//容許TS裏引入js這樣的模塊
    }
}

若是想引入lodash庫,而且想讓它的非法錯誤提示出來, 首先安裝一個這樣的模塊:

npm i @types/lodash --save-dev

以下圖, 若是不向join方法傳入參數會有報錯提示

enter description here

若是還想安裝jquery庫, 那你也須要安裝對應的類型文件

npm i @types/jquery --save-dev

這個網站能夠查詢都有哪些類型文件能夠試用:

https://microsoft.github.io/TypeSearch/

WebpackDevServer

請求轉發

若是咱們如今項目內發送請求, 咱們通常會安裝一個axios庫

npm i axios --save

enter description here

在項目中咱們通常會在開發環境有一個請求api以供測試, 線上環境有一個請求api。 這時候咱們通常須要使用相對路徑寫接口地址, 可是使用相對路徑接口地址帶上的就是localhost了, 這時候咱們須要作一個代理:

webpack.config.js

devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
		hot: true,
		hotOnly: true,
		proxy: {
			'/react/api': {
				target: 'https://www.dell-lee.com',
				secure: false,
				pathRewrite: {
					'header.json': 'demo.json'
				},
				changeOrigin: true,
				headers: {
					host: 'www.dell-lee.com',
				}
			}
		}
	},

secure: false: https的接口須要設置這個
pathRewrite: 至關於想要去獲取header.json, 配置這個獲取的是demo.json。 通常用於後端接口還沒寫好的時候使用一個demo數據, 等寫好了再使用寫好的接口,只須要把這個選項註釋掉, 不用去業務代碼中再修改了。

changeOrigin: true 始終配置就行, 主要爲了有的網站使用了origin限制。

headers: 設置請求頭, 可設置host, cookie

解決單頁面應用路由問題

首先咱們須要安裝一個路由插件:

npm i react-router-dom --save

在咱們使用單頁應用時, 若是咱們要訪問list頁面, 那麼服務器會覺得咱們訪問的是一個叫list的頁面。可是咱們的文件裏並無一個list.html, 那它就會提示咱們頁面不存在。

enter description here

enter description here

想要達到咱們預期的效果, 須要配置devServer

webpack.config.js

devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
		hot: true,
		hotOnly: true,
		historyApiFallback:true,

固然你也能夠單獨設置每一個地址的訪問,如abc.html
轉發到index.html

historyApiFallback: {
			rewrites: [{
				from: /abc.html/,
				to: '/index.html'
			}]
		},
historyApiFallback只在開發環境中有效,線上環境須要和後端配合

EsLint

使用eslint檢測代碼

安裝插件

npm i eslint --save-dev
npx eslint --init

配置好文件之後能夠用此命令查看項目文件是否符合語法:

npx eslint src

安裝解析器

npm i babel-eslint --save-dev

.eslintrc.js

enter description here

vscode中的ESlint

其實咱們也能夠不借助webpack, 直接使用編輯器自帶的插件, 如vscode的Eslint插件, 這樣使用會更加方便!

enter description here

若是咱們的團隊有些規範並不想要符合airbnb它的規範, 咱們能夠這麼配置:
首先複製出規範的名稱, 以下圖:

enter description here

而後再.eslintrc.js文件的rules裏進行配置:

enter description here

假設咱們團隊有一個同窗使用的不是vscode, 他沒有這樣的語法提示, 就會跟咱們寫的代碼不同。 這時候咱們就須要藉助webpack了:

首先咱們須要安裝一個這樣的插件:

npm i eslint-loader --save-dev

而後再webpack.config.js配置

enter description here

eslint-loader  必定要寫在後面, 只有語法正確再進行轉義或者其它。由於loader是先執行後邊再執行前邊的。
Eslint安裝使用流程
  • 安裝Eslint
  • 安裝Eslint-loader
  • webpack內devServer配置overlay
  • webpack內js文件配置eslint-loader

Eslint其餘配置

webpack.config.js

{
      test: /\.js$/,
      exclude: /node_modules/,
      use: ['babel-loader', {
        loader: 'eslint-loader',
        options: {
          fix: true,
        },
        force: 'pre',
      },
      ],
    }

fix: true 自動幫你修復比較淺顯的問題
force: 'pre' 強制先執行eslint-loader

最佳實踐

enter description here

不用配置webpack, 直接使用git的鉤子, 再提交代碼時驗證語法。

webpack性能優化

1. 跟上技術的迭代(Node, Npm, Yarn)

2. 在儘量少的模塊上應用Loader

能夠幹掉的配置:

enter description here

enter description here

3. Plugin儘量精簡併確保可靠

4. resolve參數合理配置

enter description here

extensions 建議配邏輯文件, css,圖片類不要配置, 浪費性能。

5. 使用DllPlugin 提升打包速度

咱們引入了一個lodash庫, 咱們知道這個庫的文件它是不會變的, 可是每次打包都會打包它, 咱們可讓它只在第一次打包, 下次就不打包了。

首先建立一個 webpack.dll.js

const path = require('path');
const webpack = require('webpack');

module.exports = {
	mode: 'production',
	entry: {
		vendors: ['lodash'],
		react: ['react', 'react-dom']
	},
	output: {
		filename: '[name].dll.js',
		path: path.resolve(__dirname, '../dll'),
		library: '[name]'
	},
	plugins: [
		new webpack.DllPlugin({
			name: '[name]',
			path: path.resolve(__dirname, '../dll/[name].manifest.json'),
		})
	]
}

運行命令打包組件庫:

enter description here

而後安裝一個插件:

npm i  add-asset-html-webpack-plugin --save

在webpack.common.js引入

const fs = require('fs');//引入核心模塊
const AddAssetHtmlPlugin = require("add-asset-html-webpack-plugin")
const plugins = [
	new HtmlWebpackPlugin({
		template: 'src/index.html'
	}), 
	new CleanWebpackPlugin(['dist'], {
		root: path.resolve(__dirname, '../')
	})
];

const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
files.forEach(file => {
	if(/.*\.dll.js/.test(file)) {
		plugins.push(new AddAssetHtmlWebpackPlugin({
			filepath: path.resolve(__dirname, '../dll', file)
		}))
	}
	if(/.*\.manifest.json/.test(file)) {
		plugins.push(new webpack.DllReferencePlugin({
			manifest: path.resolve(__dirname, '../dll', file)
		}))
	}
})

module.exports = {
	plugins
}

6. 控制包大小

不要引入無用組件庫, 多使用Tree Shaking, 使用SplitChunks代碼拆分。

7. thread-loader, parallel-webpack, happypack 多進程打包

8. 合理使用SourceMap

不要太詳細,配置合適的便可

9. 結合Stats分析打包結果

10. 開發環境內存編譯, 無用插件剔除

開發環境配置模式爲development

mode: "development",

多頁面打包配置

配置entry:

enter description here

想要添加多頁面首先在src目錄增長對應的js文件,而後在entry增長入口文件

webpack.common.js

const path = require("path");
const fs = require("fs");
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require("html-webpack-plugin");
const AddAssetHtmlWebpackPlugin = require("add-asset-html-webpack-plugin");
const webpack = require("webpack");

const makePlugins = configs => {
  const plugins = [new CleanWebpackPlugin()];

  Object.keys(configs.entry).forEach(item => {
    plugins.push(
      new HtmlWebpackPlugin({
        template: "src/index.html",
        filename: `${item}.html`,
        chunks: ["runtime", "vendors", item]
      })
    );
  });

  const files = fs.readdirSync(path.resolve(__dirname, "../dll"));
  files.forEach(file => {
    if (/.*\.dll.js/.test(file)) {
      plugins.push(
        new AddAssetHtmlWebpackPlugin({
          filepath: path.resolve(__dirname, "../dll", file)
        })
      );
    }
    if (/.*\.manifest.json/.test(file)) {
      plugins.push(
        new webpack.DllReferencePlugin({
          manifest: path.resolve(__dirname, "../dll", file)
        })
      );
    }
  });

  return plugins;
};

const configs = {
  entry: {
    main: "./src/index.js",
    list: "./src/list.js"
  },
  resolve: {
    extensions: [".js", ".jsx"]
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        include: path.resolve(__dirname, "../src"),
        use: [
          {
            loader: "babel-loader"
          }
        ]
      },
      {
        test: /\.(jpg|png|gif)$/,
        use: {
          loader: "url-loader",
          options: {
            name: "[name]_[hash].[ext]",
            outputPath: "images/",
            limit: 10240
          }
        }
      },
      {
        test: /\.(eot|ttf|svg)$/,
        use: {
          loader: "file-loader"
        }
      }
    ]
  },
  optimization: {
    runtimeChunk: {
      name: "runtime"
    },
    usedExports: true,
    splitChunks: {
      chunks: "all",
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          name: "vendors"
        }
      }
    }
  },
  performance: false,
  output: {
    path: path.resolve(__dirname, "../dist")
  }
};

configs.plugins = makePlugins(configs);

module.exports = configs;

如何編寫一個loader

首先建立一個文件夾 make-loader, 對項目初始化, 而後安裝webpack

npm init -y
npm i webpack webpack-cli --save-dev

建立文件夾 loader, loader文件夾內建立文件replaceLoader.js

咱們要實現的目標是若是發現咱們的業務邏輯文件內有dell這個字符, 咱們要把它修改爲dellLee

//replaceLoader.js
//source 引入文件的內容
//this.query攜帶傳過來的參數,name是loader配置的name參數
//loaderUtils能夠分析參數, 好比參數是個對象的時候能夠用到
//this.callback代替return 傳遞除result,還能夠傳遞sourcemap等內容
//異步loader 使用this.async()
const loaderUtils = require('loader-utils');
module.exports = function (source) {
    const options = loaderUtils.getOptions(this);
    const callback = this.async();
    
    setTimeout(() => {
        const result = source.replace('dell',options.name);
        callback(null,result)
    }, 1000);
}
//replaceLoader.js
module.exports = function (source) {
    return source.replace('dell','world');
}

webpack.config.js

const path = require('path');
module.exports = {
    mode: "development",
    entry: {
        main: './src/index.js'
    },
    //當你引入loader的時候,會先在node_modules裏找,若是沒有回去loader裏去找
    resolveLoader: {
        modules: ['node_modules','./loaders']
    },
    module: {
        rules: [{
            test: /\.js/,
            use: [{
                loader: 'replaceLoader',
            },{
                loader: 'replaceLoaderAsync',
                options: {
                    name: 'lee'
                }
            }]
        }]
    },
    output: {
        path: path.resolve(__dirname,'dist'),
        filename: '[name].js'
    }
}

若是你的loader配置參數有些詭異, 如是一個對象, 這時候咱們可使用一個插件作分析:

npm i loader-utils --save-dev

如何編寫一個Plugin

Plugin與loader的區別

loader

當咱們在源代碼中引入一個新的js文件,或者一個其餘格式的文件的時候, 咱們可使用loader處理這個引入的文件。

Plugin

在咱們作打包的時候, 在某一個具體時刻上。 好比說, 當我打包結束後,我要自動生成一個html文件, 這時候咱們就可使用一個html-webpack-plugin的插件。它會在打包結束後生成html文件。
Plugin能夠在咱們打包過程的某個時刻想作一些事情。

首先咱們初始化一個項目, 安裝webpack和webpack-cli。
若是咱們想要生成帶版權的文件, 能夠這麼作:

建立文件夾plugins, plugins文件夾內建立copyright-webpack-plugin.js

//options是plugin配置傳過來的參數
//compiler是webpack的實例,存儲了咱們webpack相關各類各樣的配置文件,打包過程,等等一系列的內容。
//鉤子, 指某個時刻會自動執行的函數。 如vue生命週期
//emit  當你把打包資源放到目標文件夾的時刻。它是一個異步的鉤子。 能夠在後面寫一個tabAsync. 他有兩個參數 第一個是插件名字,第二個是剪頭函數
//compile  同步的鉤子, 後面跟tap, 箭頭函數只傳compilation
//compilation 和compiler不同, 只存放此次打包的相關內容
//compilation.assets 打包生成的內容
class CopyrightWebpackPlugin {
    // constructor(options) {
    // }
    apply(compiler) {
        compiler.hooks.compile.tap('CopyrightWebpackPlugin',(compilation)=>{
            console.log('compiler');
        })
        compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin',(compilation,cb) => {
            compilation.assets['copyright.txt'] = {
                source: function() {
                    return 'copyright by dell lee'
                },
                size: function() {
                    return 21;
                }
            };
            cb();
        })
    }
}

module.exports = CopyrightWebpackPlugin

在webpack.config.js配置:

const path = require('path');
const CopyRightWebpackPlugin = require('./plugins/copyright-webpack-plugin')
module.exports = {
    mode: 'development',
    entry: {
        main: './src/index.js'
    },
    plugins: [
        new CopyRightWebpackPlugin()
    ],
    output: {
        path: path.resolve(__dirname,'dist'),
        filename:'[name].js'
    }
}

node調試工具

enter description here

--inspect 開啓node調試工具
--inspect-brk webpack執行命令的第一行打斷點

輸入 npm run debug後打開瀏覽器, 點擊控制檯左上角node小圖標

enter description here

這時候咱們就能夠看到插件的詳細信息了

可在watch增長compilation的監控

enter description here

Bundler 源碼編寫

模塊分析

Vue CLI 3 的配置方法

Vue 內的Webpack設計理念是讓咱們用的更爽, 即便是webpack小白用戶咱們也可以輕鬆使用。

若是咱們想配置webpack, 須要在項目的根目錄建立一個vue.config.js
這裏的配置和webpack並不同, 它對webpack的配置進行了大量的封裝, 若是咱們須要配置, 可參考腳手架的配置參考:https://cli.vuejs.org/zh/config/

若是咱們想實現原生的webpack, 在腳手架參考文檔使用configureWebpack 便可。

相關文章
相關標籤/搜索