webpack4功能配置劃分細化(三)

前言

以前一段時間工做緣由把精力都放在小程序上,趁如今有點空閒時間,恰好官方文檔也補充完整了,我準備重溫一下 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

alias.js

路徑簡化單獨一個配置文件方便查找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")
};

rules.js

規則處理單獨一個模塊,實在太多了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"]
  }
];

webpack.common.js

如今改改路徑和引入,瞬間清爽不少,有個地方須要注意的是如今配置文件和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
  }
};

package.json

也稍微改一下執行路徑,換個更加合適的命令名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熱更新

引入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);

而後從新執行命令測試便可

HTMl&CSS熱更新

若是足夠認真的話大家會發現如今若是修改樣式以後代碼會更新,可是瀏覽器不會自動刷新了.

那是由於熱更新的代碼是輸出在內存中,而咱們以前引入了mini-css-extract-plugin插件提取css單獨合併一個模塊以後,儘管代碼也會更新,可是html引用的link沒有改變因此沿用的仍是更新前的樣式,html更新暫時沒找到方法,可是不影響React開發,而css更新咱們能夠用過環境配置,不提取樣式解決.

固然,若是真的須要實現html更新的話,能夠簡單粗暴的換回全局刷新便可

hot: true,
// hotOnly: true

rules.js

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"]
  }
]

webpack.common.js

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 use  filename: "[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改變而從新打包,這裏簡單說一下幾種經常使用變量的區別

  1. hash: 和整個項目構建相關而且所有文件公用相同hash值,即沒有緩存效果只適用於開發階段
  2. chunkhash: 根據入口依賴文件解析構建對應的chunk生成對應的hash值,能夠保證正常業務修改不影響公共代碼,由於公共代碼屬於一個單獨模塊,可是樣式被打包進業務模塊因此二者公用同一個chunkhash.
  3. contenthash: 樣式模塊根據自身內容而生成,作到不被業務代碼改變而影響

由於對應打包路徑換了一下,因此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"
]

具體緣由能夠看

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()
  ]
});

CSS加強

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  after  css-loader and  style-loader, but  before other preprocessor loaders like e.g  sass|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

相關文章
相關標籤/搜索