從零開始webpack搭建react,redux應用

從零開始webpack搭建react,redux應用

前言:

使用webpack已經有些年頭了,可是對於其中的一些基本配置仍是隻知其一;不知其二。爲了成爲一名優秀的webpack配置工程師,也是學習了一把webpack,react的配置,特分享這次經歷,並記錄當中遇到的一些問題。固然如今的配置只是很基礎的,但願在之後的工做經歷中,多多探索,把一些webpack優化,react,redux最佳實踐,都加入到其中。javascript

文章目錄

  • webpack基礎配置
  • 配置react, less
  • 引入antd,
  • react-router的使用
  • react-redux
  • redux異步中間件的選擇 thunk/saga
  • 項目優化:MiniCssExtractPlugin,路由切割懶加載,postcss-loader, url-loader, hmr,tree shaking,
  • devserver proxy,本地mock數據
  • lint & prettier
  • 項目部署腳本

一. webpack基礎配置

學習一個新技術,最好的獲取方式即是閱讀官方文檔。(https://www.webpackjs.com/gui...)。通讀之後,總結爲如下幾個要點。css

  1. 初始化項目,安裝依賴。
npm init -y
npm install webpack webpack-cli --save-dev
  1. 配置文件
// webpack.base.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, '../dist'),
  },
};
// package.json
"scripts": {
    "dev": "webpack --config webpackconfig/webpack.base.js",
},
// dist/index.html
<!doctype html>
<html>
<head>
    <title>hyt</title>
</head>
<body>
<script src="./main.bundle.js"></script>
</body>
</html>
// src/index.js
function component() {
    var element = document.createElement('div');
    element.innerHTML = 'hello world hyt';
    return element;
}

document.body.appendChild(component());
  1. 接下來運行 npm run dev,查看dist下輸出,發現多了一個main.bundle.js文件,打開咱們新建的index.html文件,能夠看到以下,說明咱們的webpack基礎打包已經可以使用了。

  1. 若是咱們更改了一個入口起點的名稱,或者針對多入口添加了一個新的名稱,又須要咱們手動去index.html中去更改,咱們可使用HtmlWebpackPlugin動態生成index.html.

固然,避免咱們每次手動去清空dist文件下的內容,可使用clean-webpack-plugin插件幫助清空。html

npm install html-webpack-plugin clean-webpack-plugin

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

module.exports = {
  entry: './src/index.js',
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, '../dist'),
  },
  plugins: [
     new CleanWebpackPlugin(),
     new HtmlWebpackPlugin({
       title: 'Output Management'
     })
  ],
};

這裏能夠看到,HtmlWebpackPlugin已經幫助咱們生成了html文件。java

  1. 如上,咱們已經掌握了webpack打包編譯的基本使用。

可是在平常開發中,每次修改完代碼都須要手動執行webpack打包命令,很繁瑣。這時候能夠採用 watch或者webpack-dev-server或者webpack-dev-middleware方法實現。較爲經常使用的是使用webpack-dev-server,不只提供一個簡單的 web 服務器,而且可以實時從新加載。node

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

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

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "[name].bundle.js",
    path: path.resolve(__dirname, "../dist"),
  },
  devServer: {
    contentBase: './dist',
    open: true,
    port: 8888,
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: "Output Management",
    }),
  ],
};

修改package.jsonreact

"scripts": {
    "dev": "webpack-dev-server --config webpackconfig/webpack.base.js",
    "watch": "webpack --config webpackconfig/webpack.base.js --watch"
  },

執行 npm run dev,看看效果。webpack

  1. webpack-dev-server當然好用,可是隻適用於開發環境,在生產環境中,咱們的目標則轉向於關注更小的 bundle,更輕量的 source map,以及更優化的資源,以改善加載時間。因此咱們能夠根據不一樣的環境,加載不一樣的webpack配置。

webpack.base.js是通用配置,webapck.dev.js中是開發環境配置,webapck.prod.js是生產環境配置。webpack-merge能夠幫住咱們很好的合併配置。nginx

接下來拆分配置:git

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

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "[name].bundle.js",
    path: path.resolve(__dirname, "../dist"),
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: "Output Management",
    }),
  ],
};
// webpack.dev.js
const { merge } = require("webpack-merge");
const base = require("./webpack.base");

module.exports = merge(base, {
  mode: "development",
  devtool: "inline-source-map",
  devServer: {
    contentBase: "./dist",
    open: true,
    port: 8888,
  },
});
const { merge } = require("webpack-merge");
const webpack = require("webpack");
const base = require("./webpack.base");

module.exports = merge(base, {
  mode: "production",
  devtool: "source-map",
  plugins: [
    new webpack.DefinePlugin({
      "process.env.NODE_ENV": JSON.stringify("production"),
    }),
  ],
});
// package.json
"scripts": {
    "dev": "webpack-dev-server --config webpackconfig/webpack.dev.js",
    "watch": "webpack --config webpackconfig/webpack.base.js --watch",
    "prod": "webpack --config webpackconfig/webpack.prod.js"
},

到目前爲止,一個小型的webpack打包應用已經構建好了。接下來進入webpack應用中,引入react, css, less的處理。github

二. 引入React, 處理css, less

  1. 安裝React ,React-dom
npm install react react-domm

修改src/index.js,改成react組件格式代碼。

import React from "react";
import ReactDOM from "react-dom";

const App = () => {
  return <div>hello world hyt</div>;
};

ReactDOM.render(<App />, document.getElementById("root"));

由於react-dom的渲染節點,須要掛在已經存在的id=root節點上,因此咱們須要在生成的index.html中提早寫入 root節點。此操做能夠搭配以前提到的HtmlWebpackPlugin完成。添加template模板。

// src/template.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>


// webpack.base.js
new HtmlWebpackPlugin({
  title: 'hyt Management',
  template: './src/template.html',
}),

接下來運行,npm run dev,果真,報錯了。

提示咱們,應該須要專門的loader去處理咱們的js/jsx文件。這時候,就是大名鼎鼎的babel登場了。babel能夠幫助咱們進行js文件的編譯轉換。

  1. babel

除了幫助咱們對於高版本js語法轉換之外,還能夠處理react的jsx寫法。

npm install babel-loader @babel/preset-env @babel/preset-react @babel/core

更改webpack.base.js中rules規則。

module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: [{ loader: "babel-loader" }],
      },
    ],
},

根目錄新增.babelrc配置文件

{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}

接下來打包運行,npm run dev ,發現瀏覽器中終於顯示了<div>hello world hyt</div>的dom(爲了顯示一行dom,咱們費了這麼大的功夫,不得不吐槽)。

  1. 接下來給頁面加點樣式。

有了剛纔js打包報錯的經驗,應該明白,要想加入css文件,也須要有專門的loader去處理css文件,得以運行。

npm install css-loader style-loader

css-loader處理css文件爲webpack可識別打包的,style-loader插入到頁面style中。

rules: [
  {
    test: /\.(js|jsx)$/,
    exclude: /node_modules/,
    use: [{ loader: "babel-loader" }],
  },
  {
    test: /\.css$/,
    use: [
      {
        loader: "style-loader",
      },
      {
        loader: "css-loader",
      },
    ],
  },
]
// src/index.js

import "./style.css";

const App = () => {
  return <div className="hello">hello world hyt</div>;
};

// src/style.css
.hello {
  font-size: 30px;
  color: blue;
}

嗯,能夠看到頁面中有顏色了。。

這時候思考一個問題,假如在咱們其餘組件中,也有一樣名字的class,再其對應的css文件中,寫了不一樣的樣式,會有什麼結果,實驗一下。

// src/components/about/index.js
import React from "react";
import "./style.css";

const About = (props) => {
  return <div className="hello">About</div>;
};

export default About;
// src/components/about/style.css
.hello {
    color: red;
}
// src/index.js

import About from "./components/about";

<About />

看下頁面的展現,

發現color: red的樣式並無生效,打開控制檯看下打包後的樣式,名字同樣的class,樣式被覆蓋了。

因此這個時候,就引入css modules的概念了,經過css-loader的配置,幫助咱們實現css模塊化。

{
    test: /\.css$/,
    use: [
      {
        loader: "style-loader",
      },
      {
        loader: "css-loader",
        options: {
          modules: {
            localIdentName: "[name]__[local]--[hash:base64:5]", // css-loader >= 3.x,localIdentName放在modules裏
          },
        },
      },
    ],
}

更改js文件中引入方式。

import style from "./style.css";
const About = (props) => {
  return <div className={style["hello"]}>About</div>;
};

index.js中同理

emm,樣式果真生效了

  1. less

既然都用到css了,和不使用使用預處理less呢,可以更加提效咱們的開發。使用步驟和css大體相同,秩序多家less-loader先把less文件作一次轉換,再走css-loader的流程。大概配置以下

npm install less-loader
{
    test: /\.less$/,
    use: [
      {
        loader: "style-loader", // creates style nodes from JS strings
      },
      {
        loader: "css-loader", // translates CSS into CommonJS
        options: {
          modules: {
            localIdentName: "[name]__[local]--[hash:base64:5]", // css-loader >= 3.x,localIdentName放在modules裏  https://github.com/rails/webpacker/issues/2197
          },
        },
      },
      {
        loader: "less-loader", // compiles Less to CSS
            options: {
              lessOptions: { javascriptEnabled: true },// less@3.x,須要開啓 配置項 javascriptEnabled: true
            },
      },
    ],
  },

把About中的css文件改成less使用便可。接下來能夠安心的寫代碼了。

三. Antd的使用,以及less的分別處理

爲了提升咱們的開發效率,在項目中引入antd組件庫。

兩種方法,全量引入css;或按需加載。(antd 4.x 的 JS 代碼默認支持基於 ES modules 的 tree shaking。)https://ant.design/docs/react...

採用按需加載的方法來構建項目。

npm install antd babel-plugin-import

{
  "presets": ["@babel/preset-env", "@babel/preset-react"],
  "plugins": [
    [
      "import",
      {
        "libraryName": "antd",
        "libraryDirectory": "es",
        "style": true // `style: 'css'` 會加載 css 文件
      }
    ]
  ]
}

發現樣式並無加載成功。

緣由是咱們剛纔在處理less文件時,沒有區分src 和 node_modules,致使antd的class也加了modules,沒有加載到正確的樣式。修改less loader爲

{
    test: /\.less$/,
    exclude: /node_modules/, // 這裏作了修改
    use: [
      {
        loader: "style-loader", // creates style nodes from JS strings
      },
      {
        loader: "css-loader", // translates CSS into CommonJS
        options: {
          modules: {
            localIdentName: "[name]__[local]--[hash:base64:5]", // css-loader >= 3.x,localIdentName放在modules裏  https://github.com/rails/webpacker/issues/2197
          },
        },
      },
      {
        loader: "less-loader", // compiles Less to CSS
        options: {
          lessOptions: { javascriptEnabled: true },
        },
      },
    ],
  },
  {
    test: /\.less$/,
    include: /node_modules/, // 這裏作了修改
    use: [
      {
        loader: "style-loader", // creates style nodes from JS strings
      },
      {
        loader: "css-loader", // translates CSS into CommonJS
      },
      {
        loader: "less-loader", // compiles Less to CSS
        options: {
          lessOptions: { javascriptEnabled: true },
        }, // less@3.x,須要開啓 配置項 javascriptEnabled: true, less-loader高版本須要lessOptions。
      },
    ],
  },

四. React-Router

接下來引入React-Router實現單頁面應用。

具體用法可參考 https://reacttraining.com/rea...

npm install react-router-dom

修改index.js文件

import { BrowserRouter } from "react-router-dom";
import Routes from "./Routes";

const App = () => {
  return (
    <BrowserRouter>
      <Routes />
    </BrowserRouter>
  );
};

新建Routes.js

import React from "react";
import { Switch, Route, Link, Redirect } from "react-router-dom";
import About from "./components/about";
import User from "./components/user";

const Routes = () => {
  return (
    <div>
      <nav>
        <ul>
          <li>
            <Link to="/about">About</Link>
          </li>
          <li>
            <Link to="/user">User</Link>
          </li>
        </ul>
      </nav>
      <Switch>
        <Route path="/about" component={About} />
        <Route path="/User" component={User} />
        <Redirect to="/about" />
      </Switch>
    </div>
  );
};

export default Routes;

注意咱們使用的是BrowserRouter,本地開發webpack devserver須要開啓 historyApiFallback: true, 生產環境能夠在nginx端try_files。

單頁面應用ok了,接下來引入react-redux去管理咱們的數據流。

五. Ract-redux

爲何選擇redux來管理咱們的數據流,以及redux的設計原理,能夠查看阮一峯老師的系列文章,這裏只給出基本使用。http://www.ruanyifeng.com/blo...

幾個比較重要的概念,Provider,connect, creatStore, reducer, applyMiddleware,actions。

繼續改造文件結構及內容

npm install redux react-redux
  1. sotre
// src/store.js
import { createStore } from "redux";
import reducers from "./reducers/index";

const store = createStore(reducers, {});

export default store;
  1. reducer
// src/reducers/index.js
import { combineReducers } from "redux";

const initialState = {
  name: "hyt",
};

function home(state = initialState, action) {
  switch (action.type) {
    case "TEST_REDUCER":
      return {
        ...state,
      };
    default:
      return state;
  }
}

export default combineReducers({
  home,
});
  1. provider
// src/index.js

import { Provider } from "react-redux";
import Routes from "./Routes";
import store from "./store";

const App = () => {
  return (
    <Provider store={store}>
      <BrowserRouter>
        <Routes />
      </BrowserRouter>
    </Provider>
  );
};
  1. connect

新建容器組件container/home.js

import React from "react";
import { connect } from "react-redux";

const Home = (props) => {
  return <div>Home,{props.data.name}</div>;
};

export default connect((state) => ({ data: state.home }))(Home);
  1. 一樣在route中引入home組件。
import Home from "./containers/home";
const Routes = () => {
  return (
    <div>
      <nav>
        <ul>
          ...
          <li>
            <Link to="/home">Home</Link>
          </li>
        </ul>
      </nav>
      <Switch>
        ...
        <Route path="/home" component={Home} />
        <Redirect to="/about" />
      </Switch>
    </div>
  );
};

這是路由localhost:8080/home下就能夠顯示出 hello,hyt的數據。

  1. dispatch actions

上面已經獲取到了store中的數據,接下來dispatch去改變store中的數據,因爲組件訂閱了store(connect),頁面數據源會自動渲染變動。

6.1 添加action types常量

// src/constants/actionTypes.js
export const SET_USER_NAME = "SET_USER_NAME";

6.2 改變store的action

// src/actions/homeAction.js
import { SET_USER_NAME } from "../constants/actionsType";

export function setName(payload) {
  return { type: SET_USER_NAME, payload };
}

6.3 接受actions的reducer

// src/reducers/index.js
import { SET_USER_NAME } from "../constants/actionsType";

const initialState = {
  name: "hyt",
};

function home(state = initialState, action) {
  switch (action.type) {
    case SET_USER_NAME:
      return {
        ...state,
        name: action.payload.name,
      };
    default:
      return state;
  }
}

6.4 組件觸發actions。增長了mapDispatchToProps。props.setName()

// src/containers/home.js
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { setName } from "../actions/homeAction";

const Home = (props) => {
  useEffect(() => {
    setTimeout(() => {
      props.setName({
        name: "wjh",
      });
    }, 3000);
  }, []);
  return <div>Home,{props.data.name}</div>;
};

const mapDispatchToProps = {
  setName,
};

export default connect(
  (state) => ({ data: state.home }),
  mapDispatchToProps
)(Home);

如今頁面中的,hello,hyt 會在三秒後變成 hello,wjh。

六. redux中間件,thunk/saga

如今咱們處理的是同步數據,接下來咱們引入redux中間件,去處理異步action函數。

修改store,

npm install redux-thunk
// src/store.js
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import reducers from "./reducers/index";

const store = createStore(reducers, {}, applyMiddleware(thunk));

export default store;
// src/actions/homeAction.js
export function getName(payload) {
  return (dispatch) => {
    return Promise.resolve().then((res) => {
      dispatch({
        type: SET_USER_NAME,
        payload: {
          name: "fetch mock",
        },
      });
      return res;
    });
  };
}
// src/containers/home.js
const Home = (props) => {
  useEffect(() => {
    setTimeout(() => {
      // props.setName({
      //   name: "wjh",
      // });
      props.getName();
    }, 3000);
  }, []);
  return <div>Home,{props.data.name}</div>;
};

const mapDispatchToProps = {
  setName,
  getName,
};

頁面上已經變成了 hello,fetch mock.

saga的使用能夠直接參考 https://github.com/hytStart/J...

七. 項目優化

  1. 路由切割懶加載。使用import() + react-loadable完成。
npm install react-loadable

修改Routes中組件引入方式,達到按路由拆分
js模塊

import Loadable from "react-loadable";

const MyLoadingComponent = (props) => {
  if (props.pastDelay) {
    return <div>Loading...</div>;
  }
  return null;
};

const User = Loadable({
  loader: () => import("./components/user"),
  loading: MyLoadingComponent,
  delay: 300,
});

能夠看到控制檯js bundle加載。

  1. 熱更新HMR

因爲如今咱們每改一下代碼,均可以看到刷新一次頁面,因而以前的路由跳轉狀態、表單中填入的數據都會重置。對於開發人員過程很不方便,這時候就引出咱們的熱更新了,不會形成頁面刷新,而是進行模塊的替換。

// webpack.dev.js

module.exports = merge(base, {
  mode: "development",
  devtool: "inline-source-map",
  devServer: {
    contentBase: "./dist",
    open: true,
    port: 8888,
    historyApiFallback: true,
    hot: true, // +++++++
  },
});
// index.js

const App = () => {
  return (
    <Provider store={store}>
      <BrowserRouter>
        <Routes />
      </BrowserRouter>
    </Provider>
  );
};

++++
if (module.hot) {
  module.hot.accept();
}
++++
ReactDOM.render(<App />, document.getElementById("root"));
  1. url-loader & file-loader

如今咱們的項目中尚未專門的loader去處理圖片,

file-loader 能夠指定要複製和放置資源文件的位置,以及如何使用版本哈希命名以得到更好的緩存。此外,這意味着 你能夠就近管理圖片文件,可使用相對路徑而不用擔憂部署時 URL 的問題。使用正確的配置,webpack 將會在打包輸出中自動重寫文件路徑爲正確的 URL。

url-loader 容許你有條件地將文件轉換爲內聯的 base-64 URL (當文件小於給定的閾值),這會減小小文件的 HTTP 請求數。若是文件大於該閾值,會自動的交給 file-loader 處理。

增長以下配置

npm install file-loader url-loader
// webpack.base.js
{
    test: /\.(mp4|ogg)$/,
    use: [
      {
        loader: 'file-loader',
      },
    ],
  },
  {
    test: /\.(png|jpg|jpeg|gif|eot|svg|ttf|woff|woff2)$/,
    use: [
      {
        loader: 'url-loader',
        options: {
          limit: 8192,
        },
      },
    ],
  },
  1. MiniCssExtractPlugin

該插件將CSS提取到單獨的文件中。它爲每一個包含CSS的JS文件建立一個CSS文件。它支持CSS和SourceMap的按需加載。

4.1 使用mini-css-extract-plugin

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

修改webpack.base.js中關於css,
less的配置,替換掉style-loader(不在須要把style插入到html中,而是經過link引入)。

{
    test: /\.css$/,
    use: [
      // {
      //   loader: "style-loader",
      // },
      {
        loader: MiniCssExtractPlugin.loader,
        options: {
          esModule: true,
          hmr: process.env.NODE_ENV === "dev",
          reloadAll: true,
        },
      },
      {
        loader: "css-loader",
        options: {
          modules: {
            localIdentName: "[name]__[local]--[hash:base64:5]", // css-loader >= 3.x,localIdentName放在modules裏  https://github.com/rails/webpacker/issues/2197
          },
        },
      },
    ],
  },
  {
    test: /\.less$/,
    exclude: /node_modules/,
    use: [
      // {
      //   loader: "style-loader", // creates style nodes from JS strings
      // },
      {
        loader: MiniCssExtractPlugin.loader,
        options: {
          esModule: true,
          hmr: process.env.NODE_ENV === "dev",
          reloadAll: true,
        },
      },
      {
        loader: "css-loader", // translates CSS into CommonJS
        options: {
          modules: {
            localIdentName: "[name]__[local]--[hash:base64:5]", // css-loader >= 3.x,localIdentName放在modules裏  https://github.com/rails/webpacker/issues/2197
          },
        },
      },
      {
        loader: "less-loader", // compiles Less to CSS
        options: {
          lessOptions: { javascriptEnabled: true },
        },
      },
    ],
  },
  {
    test: /\.less$/,
    include: /node_modules/,
    use: [
      // {
      //   loader: "style-loader", // creates style nodes from JS strings
      // },
      {
        loader: MiniCssExtractPlugin.loader,
        options: {
          esModule: true,
          hmr: process.env.NODE_ENV === "dev",
          reloadAll: true,
        },
      },
      {
        loader: "css-loader", // translates CSS into CommonJS
      },
      {
        loader: "less-loader", // compiles Less to CSS
        options: {
          lessOptions: { javascriptEnabled: true },
        }, // less@3.x,須要開啓 配置項 javascriptEnabled: true, less-loader高版本須要lessOptions。
      },
    ],
  },

4.2 如上配置,增長hrm配置

hmr: process.env.NODE_ENV === "dev"

同時在package.json scripts中注入環境變量

"scripts": {
    "dev": "NODE_ENV=dev webpack-dev-server --config webpackconfig/webpack.dev.js",
    "watch": "NODE_ENV=dev webpack --config webpackconfig/webpack.base.js --watch",
    "prod": "webpack --config webpackconfig/webpack.prod.js"
},

4.3 plugin配置

plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: "Output Management",
      template: "./src/template.html",
    }),
    new MiniCssExtractPlugin({
      // Options similar to the same options in webpackOptions.output
      // both options are optional
      filename: "[name].css",
      chunkFilename: "[id].css",
    }),
  ],

到目前爲止,咱們已經根據引入文件的方式,分離除了css,作到了按需加載。可是如今能夠查看打包出來的css文件是沒有通過壓縮的。

4.4 增長optimize-css-assets-webpack-plugin來壓縮css代碼,可是這時又會出現另一個問題,optimization.minimizer會覆蓋webpack提供的默認設置,所以還需增長terser-webpack-plugin來壓縮js代碼。

npm install --save-dev optimize-css-assets-webpack-plugin terser-webpack-plugin
// webapack.base.js
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const TerserJSPlugin = require("terser-webpack-plugin");


plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: "Output Management",
      template: "./src/template.html",
    }),
    new MiniCssExtractPlugin({
      // Options similar to the same options in webpackOptions.output
      // both options are optional
      filename: "[name].css",
      chunkFilename: "[id].css",
    }),
],
optimization: {
    minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
},
  1. tree shaking

https://webpack.docschina.org...

mode: 'production'
相關文章
相關標籤/搜索