webpack模塊打包 使用 構(gòu)建

上一篇主要說(shuō)了webpack處理ES Module 模塊引入方式的打包
其實(shí)webpack也支持其他的規(guī)范打包如CommonJs,CMD,AMD...

npx 會(huì)自動(dòng)查找當(dāng)前依賴包中的可執(zhí)行文件,如果找不到,就會(huì)去 PATH 里找。如果依然找不到,就會(huì)幫你安裝。
上一篇說(shuō)到的,npx webpack index.js 意思就是npx幫你找到可執(zhí)行的webpack然后去打包index.js,當(dāng)然,我們也可以通過(guò)簡(jiǎn)單的配置webpack.config.js通過(guò)配置script命令去執(zhí)行你的打包命令,配置好后,只要執(zhí)行npm run build就可以了,代碼如下

webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

package.json

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {
    "webpack-cli": "^3.3.12",
    "webpack": "^4.44.0"
  },
  "devDependencies": {},
  "scripts": {
    "build": "webpack"
  },
  "author": "",
  "license": "ISC"
}

webpack-cli 是webpack提供的一個(gè)node工具鏈,從而是我們可以直接執(zhí)行webpack npx 等,當(dāng)你不寫配置文件的時(shí)候,webpack 會(huì)執(zhí)行他的內(nèi)部打包默認(rèn)配置。webpack.config.js是默認(rèn)寫法。假如你把配置文件寫成config.js,那么你在執(zhí)行打包命令的時(shí)候,就需要這么寫webpack --config config.js告訴webpack他的打包配置文件是config.js

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
你會(huì)發(fā)現(xiàn)打包的時(shí)候有這樣的警告,大概意思是,讓你配置mode(環(huán)境) development(代碼沒有被壓縮方便調(diào)試) 或者 production(代碼被壓縮),接下來(lái)我們配置一下

const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

執(zhí)行webpack 打包的時(shí)候輸出如下

Hash: 84bea18a4a1fab7a5786       本次打包的唯一hash
Version: webpack 4.44.0                  使用webpack的版本
Time: 73ms                                      本次打包總共耗時(shí)
Built at: 2020/07/28 下午4:24:30        編譯完日期
    Asset      Size  Chunks             Chunk Names      輸出的一下表格目錄結(jié)構(gòu)  編譯出的文件、大小、文件的id、編譯的文件名
bundle.js  6.08 KiB    main  [emitted]  main          
Entrypoint main = bundle.js
[./src/content.js] 179 bytes {main} [built]
[./src/footer.js] 176 bytes {main} [built]
[./src/header.js] 176 bytes {main} [built]
[./src/index.js] 135 bytes {main} [built]

webpack 默認(rèn)只認(rèn)識(shí)js 文件,當(dāng)引入其他文件的時(shí)候該怎么打包呢,舉個(gè)例子,打包圖片,首先安裝npm install file-loader -D然后配置module就可以打包圖片了,配置如下

const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,    // i 正則,代表結(jié)尾, 以.png .jpg等結(jié)尾的文件,同樣可以配置 txt excel 等所有文件 
        use: 'file-loader',
          options: {
            name: '[name]_[hash].[ext]',        // 圖片重命名
            outputPath: 'images/'                  // 輸入文件目錄
          }
      }
    ]
  }
}

url-loader也可以實(shí)現(xiàn)圖片打包,可以替代file-loader

const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048    //  `url-loader` 功能類似于 [`file-loader`](https://github.com/webpack-contrib/file-loader),但是在文件大?。▎挝?byte)低于指定的限制時(shí),可以返回一個(gè) DataURL,小于轉(zhuǎn)成base64打包到j(luò)s里反之輸出文件
          }
        }
      }
    ]
  }
}

打包c(diǎn)ss樣式

const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.css$/i,
        use: [ 'style-loader', 'css-loader' ]   // css-loader會(huì)分析用到了幾個(gè)css把所有的css合并成一個(gè)css, 'style-loader',會(huì)把合并好的css掛載到head標(biāo)簽里,執(zhí)行順序,從下到上,右到左
      }
    ]
  }
}

scss的使用npm install sass-loader node-sass webpack --save-dev

const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.s[ac]ss$/i,
        use: [ 'style-loader', 'css-loader', 'sass-loader' ]
      }
    ]
  }
}

做css瀏覽器兼容,這時(shí)候就得需要postcss-loader了npm i -D postcss-loader,npm i autoprefixer -Dpostcss-loader插件實(shí)現(xiàn)自動(dòng)配置私有前綴,

const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              modules: true   // 開啟模塊化,這樣樣式就用作用域,就可以在代碼中怎么引入`import style from './index.scss';`
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      }
    ]
  }
}

然后創(chuàng)建postcss.config.js引用loader插件,postcss.config.js查找規(guī)則由當(dāng)前文件向外層文件查找,找到停止,所以可以在不同文件中配置不用的配置

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

最后結(jié)合package.json中的browserslist 做樣式兼容

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {
    "webpack-cli": "^3.3.12"
  },
  "devDependencies": {
    "autoprefixer": "^9.8.5",
    "css-loader": "^4.0.0",
    "file-loader": "^6.0.0",
    "node-sass": "^4.14.1",
    "postcss-loader": "^3.0.0",
    "sass-loader": "^9.0.2",
    "style-loader": "^1.2.1",
    "url-loader": "^4.1.0",
    "webpack": "^4.44.0"
  },
  "scripts": {
    "build": "webpack"
  },
  "author": "",
  "license": "ISC",
  "browserslist": [
    "last 2 versions",
    "> 1%",
    "iOS 7",
    "last 3 iOS versions"
  ]
}

字體文件也可以通過(guò)file-loader配置

const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      }
    ]
  }
}

plugin的簡(jiǎn)單使用,如:
html-webpack-plugin 會(huì)在打包結(jié)束后,自動(dòng)生成一個(gè)html文件,并把打包生成的js自動(dòng)引入到這個(gè)html中

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

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [new HtmlWebpackPlugin()],
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      }
    ]
  }
}

loader是什么,webpack不能識(shí)別非js結(jié)尾的文件,所以就需要loader告訴webpack怎么去打包編譯,而plugin,更像是一個(gè)生命周期函數(shù),他可以在webpack運(yùn)行到某個(gè)時(shí)刻的時(shí)候,幫你做一些事情

clean-webpack-plugin使用

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

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }),
    new CleanWebpackPlugin()    // 默認(rèn)清理output.path中的文件
  ],
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      }
    ]
  }
}

多個(gè)打包入口的配置,配置多個(gè)打包入口的時(shí)候輸出文件的名字就不可以寫死了,可以使用動(dòng)態(tài)占位符,[name] [hash]等等,html-webpack-plugin也會(huì)自動(dòng)幫你引入打包出來(lái)的所有js,同時(shí)你也可以給output配置參數(shù),如publicPath輸出文件的目錄,域名等

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

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js',
    sub: './src/index.js'
  },
  output: {
    publicPath: 'http://www.cdn.cn',
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }),
    new CleanWebpackPlugin()
  ],
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      }
    ]
  }
}

sourceMap的配置
sourceMap,會(huì)在當(dāng)你打包出來(lái)的文件報(bào)錯(cuò)的時(shí)候,幫你找到對(duì)應(yīng)代碼的映射關(guān)系,從而準(zhǔn)確的定位到源代碼里面的錯(cuò)誤在哪里
開發(fā)環(huán)境推薦配置 devtool: 'cheap-module-eval-source-map' 這種打包速度快,同時(shí)錯(cuò)誤提示比較全
生產(chǎn)環(huán)境推薦配置 devtool: 'cheap-module-source-map', 便于調(diào)試,當(dāng)然你也可以配置成none 關(guān)閉映射。
cheap 只提示多好行出錯(cuò)了,不提示多少列。module除了業(yè)務(wù)代碼,一些loader里面的錯(cuò)誤也報(bào)一下。 source-map自動(dòng)生成.map文件。inline映射關(guān)系打包在代碼里 。eval把對(duì)應(yīng)的代碼和source-map一起執(zhí)行,提高打包效率
參考文檔

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

module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',    // 參打開代碼映射,參數(shù)none 關(guān)閉
  entry: {
    main: './src/index.js',
    sub: './src/index.js'
  },
  output: {
    publicPath: './',
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }),
    new CleanWebpackPlugin()
  ],
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      }
    ]
  }
}

output.library和output.libraryTarget屬性可能大家都會(huì)比較陌生,因?yàn)橐话闳绻辉陧?xiàng)目中使用 webpack 不需要關(guān)注這兩個(gè)屬性,但是如果是開發(fā)類庫(kù),那么這兩個(gè)屬性就是必須了解的。

umd

(function webpackUniversalModuleDefinition(..){..})(..)

amd

define(['demo'], function(demo) {
  demo();
});

commonjs

const demo = require('demo');
demo();

es module

import demo from 'demo';
demo();

大家思考一下,為什么這個(gè)類庫(kù)能支持不同方式的引入?如何實(shí)現(xiàn)的?這就是 webpack 配置output.library和output.libraryTarget提供的功能。

熱更新
webpack --watch 最簡(jiǎn)單的

webpack-dev-server

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

module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',    // 參打開代碼映射,參數(shù)none 關(guān)閉
  entry: {
    main: './src/index.js',
    sub: './src/index.js'
  },
  devServer: {
    contentBase: './dist',
    open: true
  },
  plugins: [
    new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }),
    new HtmlWebpackPlugin({
      title: 'Development',
      template: 'src/index.html'
    })
  ],
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      }
    ]
  }
}
運(yùn)行 "start": "webpack-dev-server ",

使用webpack-dev-middleware實(shí)現(xiàn)一個(gè)webpack-dev-server, 在node中使用webpack

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

module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',    // 參打開代碼映射,參數(shù)none 關(guān)閉
  entry: {
    main: './src/index.js',
    sub: './src/index.js'
  },
  devServer: {
    contentBase: './dist',
    open: true
  },
  plugins: [
    new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }),
    new HtmlWebpackPlugin({
      title: 'Development',
      template: 'src/index.html'
    })
  ],
  output: {
    publicPath: '/',        // 指向跟目錄。對(duì)應(yīng)server.js   一個(gè)node服務(wù) 
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      }
    ]
  }
}

server.js

const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('./webpack.config');
const complier = webpack(config);  // 返回一個(gè)編譯器

const app = express();
app.use(webpackDevMiddleware(complier, {
  publicPath: config.output.publicPath
}))

app.listen(3000, () => {
  console.log('server in running!!!');
})

運(yùn)行 "middleware": "node server.js"

Hot Module Replacement 熱模塊替換HMR,比如我更改了頁(yè)面的樣式,webpack檢測(cè)到代碼變化,會(huì)給我們重新打包編譯,導(dǎo)致,頁(yè)面重新加載,然而這并不是我們想要的,我們就可以通過(guò)HMR進(jìn)行熱模塊替換,dom不變只改變樣式,不觸發(fā)所有文件的重新打包。
當(dāng)你使用了HMR的時(shí)候,樣式改變不會(huì)重新加載整個(gè)html。只會(huì)替換css。當(dāng)多個(gè)js模塊引用,其中一個(gè)js模塊發(fā)生改變的時(shí)候,這個(gè)這么寫,從而只更新這一個(gè)模塊。比如在寫vue項(xiàng)目中會(huì)有這樣類似的功能但是我們代碼中并沒有寫類似module.hot.accept這樣的代碼,這是因?yàn)関ue-loader已經(jīng)替我們做了這一步

if(module.hot) {
  module.hot.accept('監(jiān)聽變化的模塊地址', () => { 
    代碼發(fā)生改變時(shí)所執(zhí)行的事 
  })
}
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',    // 參打開代碼映射,參數(shù)none 關(guān)閉
  entry: {
    main: './src/index.js',
    sub: './src/index.js'
  },
  devServer: {
    contentBase: './dist',
    open: true,
    hot: true,
    hotOnly: true
  },
  plugins: [
    new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }),
    new HtmlWebpackPlugin({
      title: 'Development',
      template: 'src/index.html'
    }),
    new webpack.HotModuleReplacementPlugin() 
  ],
  output: {
    publicPath: '/',        // 指向跟目錄。對(duì)應(yīng)server.js   一個(gè)node服務(wù) 
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      }
    ]
  }
}

使用babel處理ES6+語(yǔ)法
npm install --save-dev babel-loader @babel/core babel-loade把babel和webpack建立連接,通信,@babel/core是babel的核心包
npm install @babel/preset-env --save-dev @babel/preset-env是轉(zhuǎn)化為es5代碼,里面包含了所有es6+轉(zhuǎn)換成es5的翻譯規(guī)則。
npm install --save @babel/polyfill 對(duì)低版本瀏覽器的語(yǔ)法兼容,全局引入import "@babel/polyfill";然后在webpack中配置按需加載
npm install --save-dev @babel/plugin-transform-runtime npm install --save @babel/runtime npm install --save @babel/runtime-corejs2

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

module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',    // 參打開代碼映射,參數(shù)none 關(guān)閉
  entry: {
    main: './src/index.js',
    sub: './src/index.js'
  },
  devServer: {
    contentBase: './dist',
    open: true,
    hot: true,
    hotOnly: true
  },
  plugins: [
    new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }),
    new HtmlWebpackPlugin({
      title: 'Development',
      template: 'src/index.html'
    }),
    new webpack.HotModuleReplacementPlugin() 
  ],
  output: {
    publicPath: '/',        // 指向跟目錄。對(duì)應(yīng)server.js   一個(gè)node服務(wù) 
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      },
      { test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        options: {   // options里面的內(nèi)容也可以都提出去,提到根目錄下的.babelrc,就不需要在這里寫了
          // presets: [["@babel/preset-env", {
          //   targets: {
          //     chrome: "58",               // 如果chrome 58版本支持,就不去轉(zhuǎn)換了
          //     ie: "11"
          //   },
          //   useBuiltIns: "usage",     // 會(huì)幫你自動(dòng)引入 @babel/polyfill
          //   "corejs": "3"
          // }]]
          plugins: [["@babel/plugin-transform-runtime", {  // 在寫類庫(kù)的時(shí)候可以這么配置,避免全局引入@babel/polyfill從而造成數(shù)據(jù)污染,此方式會(huì)以閉包等注入有作用域,不會(huì)污染到其他地兒
            "absoluteRuntime": false,
            "corejs": 2,    //  當(dāng)頁(yè)面需要轉(zhuǎn)換語(yǔ)法的時(shí)候幫你裝換
            "helpers": true,
            "regenerator": true,
            "useESModules": false,
            "version": "7.0.0-beta.0"
          }]]
        }
      }
    ]
  }
}

安裝react打包需要的環(huán)境
npm i react react-dom --save npm install --save-dev @babel/preset-react

react 代碼

import React, { Component } from 'react';
import ReactDom from 'react-dom';

class App extends Component {
  render() {
    return <div>hello react</div>
  }
}

ReactDom.render(<App/>, document.getElementById('app'));

npm install --save-dev @babel/preset-react

Tree Shaking

Tree Shaking 只支持ES Module的引入方式(底層靜態(tài)引入,而CommonJS引入屬于動(dòng)態(tài)引入,tree shaking只支持靜態(tài)引入),他會(huì)把,你項(xiàng)目中沒有用到的模塊剔除掉,mode: 'production'此環(huán)境自動(dòng)就配置了

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

module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',    // 參打開代碼映射,參數(shù)none 關(guān)閉
  entry: {
    main: './src/index.js',
    sub: './src/index.js'
  },
  devServer: {
    contentBase: './dist',
    open: true,
    hot: true,
    hotOnly: true   // 如果沒有寫HMR 熱模塊相關(guān)的代碼,可以把此配置去掉,自動(dòng)刷新瀏覽器
  },
  plugins: [
    new CleanWebpackPlugin(['dist'],{
      root: path.resolve(__dirname, '/),   // 設(shè)置要?jiǎng)h除文件的根目錄
       cleanStaleWebpackAssets: false 
    }),
    new HtmlWebpackPlugin({
      title: 'Development',
      template: 'src/index.html'
    }),
    new webpack.HotModuleReplacementPlugin()   // HMR 熱模塊替換
  ],
  // 在 package.json中配置 "sideEffects": [ '*.css', '@babel/polyfill' ],  // 避免那種直接import引入的文件被忽略,如 import '@babel/polyfill'
  optimization: {   // tree shaking 支持,production 模式中只不需要配置這塊,只需要package.json中配置 "sideEffects"
    usedExports: true
  },
  output: {
    publicPath: '/',        // 指向跟目錄。對(duì)應(yīng)server.js   一個(gè)node服務(wù) 
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
            limit: 2048
          }
        }
      },
      {
        test: /\.scss$/i,
        use: [
          'style-loader',
          { 
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            } 
          },
          'sass-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.(eot|woff2|woff|ttf|svg)$/,
        use: [
          'file-loader',
        ]
      },
      { test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        // options: {   // options里面的內(nèi)容也可以都提出去,提到根目錄下的.babelrc,就不需要在這里寫了
        //   // presets: [["@babel/preset-env", {
        //   //   targets: {
        //   //     chrome: "58",               // 如果chrome 58版本支持,就不去轉(zhuǎn)換了
        //   //     ie: "11"
        //   //   },
        //   //   useBuiltIns: "usage",
        //   //   "corejs": "3"
        //   // }]]
        //   plugins: [["@babel/plugin-transform-runtime", {  // 在寫類庫(kù)的時(shí)候可以這么配置,避免全局引入@babel/polyfill從而造成數(shù)據(jù)污染,此方式會(huì)以閉包等注入有作用域,不會(huì)污染到其他地兒
        //     "absoluteRuntime": false,
        //     "corejs": 2,
        //     "helpers": true,
        //     "regenerator": true,
        //     "useESModules": false,
        //     "version": "7.0.0-beta.0"
        //   }]]
        // }
      }
    ]
  }
}

development 和 production 模式的區(qū)分打包

可以創(chuàng)建 webpack.dev.js 和 webpack.prod.js 兩個(gè)配置文件來(lái)區(qū)分開發(fā)和生產(chǎn),通過(guò)package.json里面配置命令 --config webpack.dev.js 等等
將一些公用的代碼,提取到webpack.common.js文件中,避免冗余

npm install webpack-merge -D通過(guò)這個(gè)插件,將webpack.common.js中的代碼,分別和dev和prod中的代碼進(jìn)行合并

const merge = require('webpack-merge)
const commonConfig = require('./webpack.common.js')
...
module.exports = merge(commonConfig, devConfig)

webpack 和 Code Splitting 代碼分割

如果代碼不分割,打包的體積會(huì)很大,加載速度變慢,比如一些插件庫(kù) loadsh 等是不需要被時(shí)刻打包的,手動(dòng)代碼分割,可以自entry里面做多個(gè)打包入口,這樣就可以打包成多個(gè)文件

自動(dòng)配置的話,可以在之前的optimization里面做一些配置
代碼分割和webpack沒有任何關(guān)系,只是webpack 可以更優(yōu)雅的實(shí)現(xiàn)代碼分割

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

異步導(dǎo)入 實(shí)驗(yàn)性語(yǔ)法,需要用babel去解析,安裝npm install babel-plugin-dynamic-import-webpack -D
.babelrc 配置

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "chrome": "58",
          "ie": "11"
        },
        "useBuiltIns": "usage",
        "corejs": "3"
      }
    ],
    "@babel/preset-react"
  ],
  plugins: ['dynamic-import-webpack']
}

/* webpackPrefetch: true */ 表示網(wǎng)絡(luò)空閑時(shí)開始加載,首屏渲染優(yōu)化點(diǎn),這樣點(diǎn)擊事件觸發(fā)的時(shí)候,不會(huì)有體驗(yàn)問(wèn)題,應(yīng)用場(chǎng)景,首頁(yè)有登錄彈窗的時(shí)候,可以把,登錄框采用異步加載,優(yōu)化首屏渲染速度

function getComponent() {
  return import(/* webpackPrefetch: true */'loadsh').then(({ default: _ }) => {
    var element = document.createElement('div');
    element.innerHTML = _.join([1,2,3], '-')
  })
}
document.addEventListener('click',() => {
  getComponent().then(element => {
    document.body.appendChild(element) 
  })
})

webpack 會(huì)對(duì)同步和異步載入的代碼,都會(huì)進(jìn)行分割(默認(rèn)參數(shù)async 只對(duì)異步代碼進(jìn)行分割,因?yàn)閣ebpack更多希望你在代碼書寫的時(shí)候就采用異步,優(yōu)雅的代碼)。代碼分割,和webpack無(wú)關(guān),webpack實(shí)現(xiàn)代碼分割的方式有兩種,1同步代碼的分割,只需要在webpack配置文件中配置,optimization的配置,2異步代碼,無(wú)需任何配置,會(huì)自動(dòng)進(jìn)行代碼的分割

splitChunksPlugin 配置參數(shù)的詳解

使用官方提供的異步導(dǎo)入兼容插件
npm install --save--dev @babel/plugin-syntax-dynamic-import

更改chunk名字,可以使用注釋的寫法 /* webpackChunkName: 'loadsh' */

默認(rèn)配置

module.exports = {
  /...
// todo 待后續(xù)仔細(xì)研究
  optimization: {   // tree shaking 支持,production 模式中只不需要配置這塊,只需要package.json中配置 "sideEffects"
    usedExports: true,
    splitChunks: {
      chunks: 'async',     // 只對(duì)異步代碼生效  參數(shù)all 同步異步都會(huì)分割
      minSize: 20000,   // 大于 20000 字節(jié)才去做分割
      minRemainingSize: 0,
      maxSize: 9000000,   // 如果被分割的代碼大于9000000 字節(jié),會(huì)對(duì)這些文件嘗試進(jìn)行二次分割
      minChunks: 1,   // 代碼被使用多少次之后,才進(jìn)行代碼分割
      maxAsyncRequests: 30,   // 同時(shí)加載的模塊最多是30個(gè),超過(guò)30個(gè)就不會(huì)做代碼分割了
      maxInitialRequests: 30,  // 入口文件,最多可以分割30個(gè),多了就不在分割 
      automaticNameDelimiter: '~',  // 打包出文件,組合文件名的連接符
      enforceSizeThreshold: 50000,
      cacheGroups: {   // 同步代碼會(huì)走這里,如果是node-modules下的會(huì)分割在defaultVendors組中, cacheGroups和 chunks配合使用
        defaultVendors: {  // cacheGroups 緩存組,打包多個(gè)文件的時(shí)候,先放在緩存組里面,最后再合并輸出
          test: /[\\/]node_modules[\\/]/,
          priority: -10,    // 多個(gè)組之間的匹配規(guī)則,值越大優(yōu)先級(jí)越高
          filename: 'vendors.js'
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,    // 如果之前這個(gè)模塊被打包過(guò),那么再次打包的時(shí)候就會(huì)忽略這個(gè)模塊使用之前打包好的
          filename: 'common.js'
        }
      }
    }
  },

Lazy Loading 懶加載,Chunk是什么

打包分析,Preloading Prefetching

webpack.github.com/analyse
將打包的描述文件放在stats.json中
"build": "webpack --profile --json > stats.json",
將輸出的文件上傳到 webpack.github.com/analyse中會(huì)查看打包的一些分析依賴

首屏渲染優(yōu)化

盡量去寫,異步加載的代碼,提高代碼的利用率,覆蓋率 commd + shif + p 搜索 show cover 進(jìn)行瀏覽器覆蓋率調(diào)試
/* webpackPrefetch: true */ 表示網(wǎng)絡(luò)空閑時(shí)開始加載,首屏渲染優(yōu)化點(diǎn)
webpackPrefetch webpackPreLoad 的區(qū)別 webpackPrefetch會(huì)等待頁(yè)面加載完之后頁(yè)面空閑的時(shí)候再加載你的代碼,webpackPreLoad不會(huì)等待會(huì)一起加載,所以還是webpackPrefetch比較合適(注意瀏覽器的兼容)

Css文件的代碼分割

Css文件的代碼分割 MiniCssExtractPlugin替換style-loader, 在使用此插件做css 代碼分割的時(shí)候,一定要主要的是tree shaking 中要排除 *.css 一面,import引入的css 被忽略

Webpack與瀏覽器緩存(Caching)

contenthash 如果打包出的文件沒有變化此值不變,有變化才會(huì)變

output: {
  filename: '[name].[contenthash].js',
  chunkFilename: '[name].[contenthash].js'
}  
// 兼容低版本,防止contenthash不改變內(nèi)容也變
// 低版本中對(duì)于各個(gè)js之間的依賴關(guān)系(manifest)有可能會(huì)變,所以配置如下,將存在與各個(gè)js之間的依賴關(guān)系提取出來(lái),這樣打包會(huì)多生成一個(gè)runtime.js的文件
optimization: {   
  runtimeChunk: {
    name: 'runtime'
  }
}

Shimming 墊片

假如你引用的一個(gè)npm包依賴另一個(gè)文件,而猶豫webpack的模塊化打包,你在外層引入這個(gè)依賴文件是不能被npm包所找到,這時(shí)候就需要用到,墊片 webpack 提供的一個(gè)插件webpack.ProvidePlugin

// 當(dāng)你的模塊中使用到$這個(gè)變量的時(shí)候,這個(gè)插件會(huì)在編譯的過(guò)程中自動(dòng)幫你引入jquery,給你自動(dòng)添加類似  `import $ from 'jquery'` 這樣的代碼
new webpack.ProvidePlugin({
  $: 'jquery',
  _join: ['loadsh', 'join']     // 當(dāng)你使用_join的時(shí)候webpack會(huì)自動(dòng)幫你吧 loadsh中的join模塊引入進(jìn)去賦值給_join
})

一個(gè)模塊中的this始終指向模塊自身,其上下文,如果我想讓一個(gè)模塊中的this指向window呢該怎么做可以借助插件 npm install imports-loader --save-dev

module: {
  rules: [{
    test: /\.js$/,
    exclude: /node_modules/,
    use: [
      {
         loader: 'babel-loader'
      },
      {
        loader: 'imports-loader?this=>window'     //  改變了webpack的一些默認(rèn)行為
      }
    ]
  }]
}

環(huán)境變量的使用

module.exports = (env) => {
  if(env && env.production) { // 生產(chǎn)
    return merge(commonConfig, prodConfig);
  }else { // 開發(fā)
    return merge(commonConfig, devConfig);
  }
}
"build": "webpack --env.production --config ./build/webpack.common.js"  // 傳遞一個(gè)env.production全局變量

Library 的打包

上一篇webpack初識(shí)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容