以前一段時間工做緣由把精力都放在小程序上,趁如今有點空閒時間,恰好官方文檔也補充完整了,我準備重溫一下 webpack 之路了,由於官方文檔已經寫得很是詳細,我會大量引用原文描述,主要重點放在怎麼從零構建 webpack4 代碼上,這不是一個系統的教程,而是從零摸索一步步搭建起來的筆記,因此前期可能bug會後續發現繼續修復而不是修改文章.javascript
webpack4從零開始構建(一)
webpack4+React16項目構建(二)
webpack4功能配置劃分細化(三)
webpack4引入Ant Design和Typescript(四)
webpack4代碼去重,簡化信息和構建優化(五)
webpack4配置Vue版腳手架(六)css
基本已經可使用的完整配置webpack4_demo,html
繼續上回分解,咱們以前已經實現了資源處理,配置環境分開,引入React庫和babel庫,圖片優化和打包可視化,這一章咱們就將零散的文件進一步規格化配置前端
2018/12/26上傳,代碼同步到第四篇文章
2019/03/14上傳,補充代碼到第三篇文章java
咱們在根目錄單獨新建文件夾config
,將全部webpack配置文件放進去,而後改一下相對路徑的引入,接下來抽取出些配置文件單獨一個模塊管理.node
路徑簡化單獨一個配置文件方便查找react
const path = require('path'); // 建立 import 或 require 的別名,來確保模塊引入變得更簡單 module.exports = { "@": path.resolve(__dirname, "../src/"), IMG: path.resolve(__dirname, "../src/img"), STYLE: path.resolve(__dirname, "../src/style"), JS: path.resolve(__dirname, "../src/js"), ROUTER: path.resolve(__dirname, "../src/router"), PAGE: path.resolve(__dirname, "../src/page"), CMT: path.resolve(__dirname, "../src/component") };
規則處理單獨一個模塊,實在太多了webpack
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = [ { test: /\.(js|jsx)$/, // 匹配文件 exclude: /node_modules/, // 過濾文件夾 use: { loader: "babel-loader" } }, { test: /\.s?css$/, // 匹配文件 use: [ { loader: MiniCssExtractPlugin.loader, options: { // you can specify a publicPath here // by default it use publicPath in webpackOptions.output publicPath: "../" } }, // "style-loader", // 使用<style>將css-loader內部樣式注入到咱們的HTML頁面 "css-loader", // 加載.css文件將其轉換爲JS模塊 "sass-loader" // 加載 SASS / SCSS 文件並將其編譯爲 CSS ] }, { test: /\.(html)$/, use: { loader: "html-loader", options: { attrs: ["img:src", "img:data-src", "audio:src"], minimize: true } } }, { test: /\.(png|svg|jpe?g|gif)$/i, // 圖片處理 use: [ { loader: "url-loader", options: { name: "[name].[hash:5].[ext]", limit: 20 * 1024, // size <= 50kb outputPath: "img" } }, { loader: "image-webpack-loader", options: { // Compress JPEG images mozjpeg: { progressive: true, quality: 65 }, // Compress PNG images optipng: { enabled: false }, // Compress PNG images pngquant: { quality: "65-90", speed: 4 }, // Compress GIF images gifsicle: { interlaced: false }, // Compress JPG & PNG images into WEBP webp: { quality: 75 } } } ] }, { test: /\.(woff|woff2|eot|ttf|otf)$/, // 字體處理 use: ["file-loader"] }, { test: /\.xml$/, // 文件處理 use: ["xml-loader"] } ];
如今改改路徑和引入,瞬間清爽不少,有個地方須要注意的是如今配置文件和dist文件不在同一個層級,默認是不容許刪除層級之上,咱們須要開放權限
由於最近更新版本不支持之前寫法,因此替換一下git
// 清除文件 new CleanWebpackPlugin({ dangerouslyAllowCleanPatternsOutsideProject: true, cleanOnceBeforeBuildPatterns: ["../dist"], dry: true }),
const path = require("path"), HtmlWebpackPlugin = require("html-webpack-plugin"), CleanWebpackPlugin = require("clean-webpack-plugin"), MiniCssExtractPlugin = require("mini-css-extract-plugin"), alias = require("./alias"), rules = require("./rules"); module.exports = { // 入口 entry: "./src/index.js", // 輸出 output: { // 打包文件名 filename: "[name].bundle.js", // 輸出路徑 path: path.resolve(__dirname, "../dist"), // 資源請求路徑 publicPath: "" }, module: { rules }, plugins: [ // 清除文件 new CleanWebpackPlugin({ dangerouslyAllowCleanPatternsOutsideProject: true, cleanOnceBeforeBuildPatterns: ["../dist/**"], dry: false }), // 提取樣式文件 new MiniCssExtractPlugin({ // Options similar to the same options in webpackOptions.output // both options are optional filename: "style/[name].[chunkhash:8].css", chunkFilename: "style/[id].css" }), new HtmlWebpackPlugin({ // title title: "test", // 模板 template: "index.html" }) ], resolve: { // 建立 import 或 require 的別名,來確保模塊引入變得更簡單 alias } };
也稍微改一下執行路徑,換個更加合適的命令名github
"scripts": { "dev": "webpack --config ./config/webpack.dev.js", "prod": "webpack --config ./config/webpack.prod.js", "server": "webpack-dev-server --config ./config/webpack.server.js" },
爲了實現配置效果咱們須要安裝一個插件cross-env
yarn add cross-env
這是一個能夠跨平臺系統設置環境變量的庫,簡單來講就是在命令行設置變量
"scripts": { "dev": "cross-env NODE_ENV=DEV webpack --config ./config/webpack.dev.js", "prod": "cross-env NODE_ENV=PROD webpack --config ./config/webpack.prod.js", "server": "cross-env NODE_ENV=SERVER webpack-dev-server --config ./config/webpack.server.js" },
而後咱們就能在js裏獲取process.env.NODE_ENV
字段拿到咱們自定義的字段了.接下來咱們修改一下配置文件
咱們目前的圖片配置分別使用了url-loader
轉碼和image-webpack-loader
作壓縮,實際開發中咱們不須要壓縮,因此將後者抽離.
{ test: /\.(png|svg|jpe?g|gif)$/i, // 圖片處理 use: process.env.NODE_ENV === "PROD" ? [ { loader: "url-loader", options: { name: "[name].[hash:5].[ext]", limit: 20 * 1024, // size <= 50kb outputPath: "img" } }, { loader: "image-webpack-loader", options: { // Compress JPEG images mozjpeg: { progressive: true, quality: 65 }, // Compress PNG images optipng: { enabled: false }, // Compress PNG images pngquant: { quality: "65-90", speed: 4 }, // Compress GIF images gifsicle: { interlaced: false } } } ] : [ { loader: "url-loader", options: { name: "[name].[hash:5].[ext]", limit: 20 * 1024, // size <= 50kb outputPath: "img" } } ] },
引入react以後會發現如今修改js代碼會刷新,可是瀏覽器須要手動刷新纔看到效果,控制檯提示
Ignored an update to unaccepted module,The following modules couldn’t be hot updated: (They would need a full reload!)
這個問題咱們能夠經過引入react-hot-loader解決
yarn add react-hot-loader
先在.babelrc
添加配置
{ "presets": [ ["env", { modules: false }], "react" ], "plugins": ["react-hot-loader/babel"] }
而後把根組件包裹在裏面輸出,修改\src\page\main.jsx
文件
import React, { Component, Fragment } from "react"; import { Switch, Route, Redirect, Link } from "react-router-dom"; import { hot } from "react-hot-loader"; import View1 from "CMT/view1"; import View2 from "CMT/view2"; import "STYLE/style.scss"; class Main extends Component { constructor(props, context) { super(props, context); this.state = { title: "Hello World!" }; } render() { return ( <Fragment> <p>{this.state.title}</p> <Link to="/view1/">View1</Link> <Link to="/view2/">View2</Link> <Switch> <Route exact path="/" component={View1} /> <Route path="/view1/" component={View1} /> <Route path="/view2/" component={View2} /> <Redirect to="/" /> </Switch> </Fragment> ); } } export default hot(module)(Main);
而後從新執行命令測試便可
若是足夠認真的話大家會發現如今若是修改樣式以後代碼會更新,可是瀏覽器不會自動刷新了.
那是由於熱更新的代碼是輸出在內存中,而咱們以前引入了mini-css-extract-plugin
插件提取css單獨合併一個模塊以後,儘管代碼也會更新,可是html引用的link
沒有改變因此沿用的仍是更新前的樣式,html更新暫時沒找到方法,可是不影響React開發,而css更新咱們能夠用過環境配置,不提取樣式解決.
固然,若是真的須要實現html更新的話,能夠簡單粗暴的換回全局刷新便可
hot: true, // hotOnly: true
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = [{ test: /\.(js|jsx)$/, // 匹配文件 exclude: /node_modules/, // 過濾文件夾 use: { loader: "babel-loader" } }, { test: /\.s?css$/, // 匹配文件 use: [process.env.NODE_ENV !== "SERVER" ? { loader: MiniCssExtractPlugin.loader, options: { // you can specify a publicPath here // by default it use publicPath in webpackOptions.output publicPath: '../' } } : 'style-loader', // 使用<style>將css-loader內部樣式注入到咱們的HTML頁面, 'css-loader', // 加載.css文件將其轉換爲JS模塊 'sass-loader' // 加載 SASS / SCSS 文件並將其編譯爲 CSS ] }, { test: /\.(html)$/, use: { loader: "html-loader", options: { attrs: ["img:src", "img:data-src", "audio:src"], minimize: true } } }, { test: /\.(png|svg|jpe?g|gif)$/i, // 圖片處理 use: process.env.NODE_ENV === "PROD" ? [ { loader: "url-loader", options: { name: "[name].[hash:5].[ext]", limit: 20 * 1024, // size <= 50kb outputPath: "img" } }, { loader: "image-webpack-loader", options: { // Compress JPEG images mozjpeg: { progressive: true, quality: 65 }, // Compress PNG images optipng: { enabled: false }, // Compress PNG images pngquant: { quality: "65-90", speed: 4 }, // Compress GIF images gifsicle: { interlaced: false } } } ] : [ { loader: "url-loader", options: { name: "[name].[hash:5].[ext]", limit: 20 * 1024, // size <= 50kb outputPath: "img" } } ] }, { test: /\.(woff|woff2|eot|ttf|otf)$/, // 字體處理 use: ["file-loader"] }, { test: /\.xml$/, // 文件處理 use: ["xml-loader"] } ]
const path = require("path"), HtmlWebpackPlugin = require("html-webpack-plugin"), CleanWebpackPlugin = require("clean-webpack-plugin"), MiniCssExtractPlugin = require("mini-css-extract-plugin"), alias = require("./alias"), rules = require("./rules"); module.exports = { // 入口 entry: "./src/index.js", // 輸出 output: { // 打包文件名 filename: "[name].bundle.js", // 輸出路徑 path: path.resolve(__dirname, "../dist"), // 資源請求路徑 publicPath: "" }, module: { rules }, plugins: [ // 清除文件 new CleanWebpackPlugin({ dangerouslyAllowCleanPatternsOutsideProject: true, cleanOnceBeforeBuildPatterns: ["../dist/**"], dry: false }), // 提取樣式文件 new MiniCssExtractPlugin({ // Options similar to the same options in webpackOptions.output // both options are optional filename: process.env.NODE_ENV !== "PROD" ? "[name].css" : "style/[name].[contenthash].css", chunkFilename: process.env.NODE_ENV !== "PROD" ? "[id].css" : "style/[id].[contenthash].css" }), new HtmlWebpackPlugin({ // title title: "test", // 模板 template: "index.html" }) ], resolve: { // 建立 import 或 require 的別名,來確保模塊引入變得更簡單 alias } };
輸出名字那裏也換了一下,官方推薦
For long term caching usefilename: "[contenthash].css"
. Optionally add[name]
.
默認配置以下:
new MiniCssExtractPlugin({ // Options similar to the same options in webpackOptions.output // both options are optional filename: devMode ? '[name].css' : '[name].[hash].css', chunkFilename: devMode ? '[id].css' : '[id].[hash].css', })
可是這樣子即便沒改動也會致使hash改變而從新打包,這裏簡單說一下幾種經常使用變量的區別
由於對應打包路徑換了一下,因此loader也須要判斷一些路徑
{ test: /\.s?css$/, // 匹配文件 use: [ process.env.NODE_ENV !== "SERVER" ? { loader: MiniCssExtractPlugin.loader, options: { // you can specify a publicPath here // by default it use publicPath in webpackOptions.output publicPath: process.env.NODE_ENV === "DEV" ? "./" : "../" } } : "style-loader", // 使用<style>將css-loader內部樣式注入到咱們的HTML頁面, "css-loader", // 加載.css文件將其轉換爲JS模塊 { loader: "postcss-loader", options: { config: { path: "./" // 寫到目錄便可,文件名強制要求是postcss.config.js } } }, "sass-loader" // 加載 SASS / SCSS 文件並將其編譯爲 CSS ] },
大家覺得這樣就算完了?不,你打包生產環境看看
npm run prod
而後你會發現竟然沒有打包css!!??
處處查找資料發現有兩種辦法解決
修改引入方法
import "STYLE/style.scss"; -> require("STYLE/style.scss");
修改package.json
"sideEffects": [ "*.scss", "*.css" ]
具體緣由能夠看
mini-css-extract-plugin
沒有實現壓縮功能,咱們本身從新引用一個完成庫optimize-css-assets-webpack-plugin
yarn add optimize-css-assets-webpack-plugin
它會在構建期間搜索css資源而且優化壓縮處理.
而後生產配置文件修改webpack.prod.js
const merge = require("webpack-merge"), common = require("./webpack.common.js"), OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); module.exports = merge(common, { mode: 'production', // 原始源代碼 devtool: 'source-map', plugins: [ new OptimizeCssAssetsPlugin() ] });
postcss-loader能夠同經過配置加強CSS的功能,在這裏咱們先簡單使用自動補全前綴的功能,首先
yarn add postcss-loader autoprefixer
在根目錄新建postcss.config.js
做爲配置文件
const autoprefixer = require('autoprefixer'); module.exports = { plugins: [ autoprefixer({ browsers: ['iOS >= 6', 'Android >= 4', 'IE >= 9'] }) ] };
只是加載插件設定兼容的系統版本,同時也要在rules.js
修改,須要設定尋找配置的路徑
{ test: /\.s?css$/, // 匹配文件 use: [process.env.NODE_ENV !== "SERVER" ? { loader: MiniCssExtractPlugin.loader, options: { // you can specify a publicPath here // by default it use publicPath in webpackOptions.output publicPath: '../' } } : 'style-loader', // 使用<style>將css-loader內部樣式注入到咱們的HTML頁面, 'css-loader', // 加載.css文件將其轉換爲JS模塊 { loader: 'postcss-loader', options: { config: { path: './' // 寫到目錄便可,文件名強制要求是postcss.config.js } } }, 'sass-loader' // 加載 SASS / SCSS 文件並將其編譯爲 CSS ] }
注意引入位置
Use it aftercss-loader
andstyle-loader
, but before other preprocessor loaders like e.gsass|less|stylus-loader
, if you use any.
啓動服務器開發有時候須要訪問外部域名請求,可是後臺又沒幫你解決跨域問題的話,咱們能夠再配置增長一個跨域配置,以下
devServer: { // 打開模式, Iframe mode和Inline mode最後達到的效果都是同樣的,都是監聽文件的變化,而後再將編譯後的文件推送到前端,完成頁面的reload的 inline: true, // 指定了服務器資源的根目錄 contentBase: path.join(__dirname, '../dist'), // 是否開啓gzip壓縮 compress: false, port: 9000, // 是否開啓熱替換功能 // hot: true, // 是否自動打開頁面,能夠傳入指定瀏覽器名字打開 open: false, // 是否開啓部分熱替換功能 hotOnly: true, proxy: { '/api': { // 代理地址 target: 'http://alpha.xiaohuxi.cn', changeOrigin: true, // 默認狀況下,不接受運行在 HTTPS 上,且使用了無效證書的後端服務器。若是你想要接受 secure: true, // 重寫路徑 pathRewrite: { '^/api': '' }, } } },
當下全部/api
的請求都會被轉發到http://www.test.cn
地址去,更多用法參考文檔http-proxy-middleware