从零到一学会webpack 01 - webpack构建应用

532 阅读3分钟

适合什么人:对webpack有简单的了解(知道webpack的几大核心概念)

  • 入口(entry)
  • 输出(output)
  • loader
  • 插件(plugin)
  • 模式(mode)
  • 浏览器兼容性(browser compatibility)(可以先不懂)
  • 环境(environment)(可以先不懂) 但是,并不知道如何配置webpack,如果以上几个核心概念不懂,请先去看webpack官方文档

假设有这么一个场景,你想用React构建一个应用,并且执行打包后部署到服务端。(这里我假设你不使用Create-React-App来创建项目),那么你首先会遇到两个问题:浏览器无法识别.jsx文件/如何去打包文件? 这里先从这两个问题入手把那就!

因为需要编译.jsx文件所以需要用到babel

我们先创建一个React项目

目录结构为

├── public
│   └── index.html
├── src
│   ├── App.css
│   ├── App.jsx
│   └── index.js
├── package.json
├── webpack.config.js
└── yarn.lock
├──.babelrc

这是一个简单地React程序,其中 public文件夹目前只有index.html文件

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <title>React Starter</title>
</head>

<body>
  <div id="root"></div>
  <noscript>
    You need to enable JavaScript to run this app.
  </noscript>
  <script src="../dist/bundle.js"></script>
</body>

</html>

引入的script文件为build后的文件(目前根据默认build配置,生成的文件会在dist文件夹的bundle.js文件)

package.json

{
  "name": "webpack-note",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack-cli": "^4.2.0",
    "@babel/core": "^7.13.1",
    "@babel/preset-env": "^7.13.5",
    "@babel/preset-react": "^7.12.13",
    "clean-webpack-plugin": "^3.0.0",
    "html-webpack-plugin": "^4.5.0",
    "babel-loader": "^8.2.2",
    "css-loader": "^5.0.2",
    "style-loader": "^2.0.0",
    "webpack": "^5.4.0"
  },
  "dependencies": {
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-router-dom": "^5.2.0"
  }
}

.babelrc 为babel的简单配置文件

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

webpack.config.js为webpack配置文件

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

module.exports = {
  entry: "./src/index.js",// 执行入口
  mode: "development", // 开发环境
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/, // 配置的js/jsx文件的loader
        exclude: /(node_modules|bower_components)/, // babel不编译该文件夹
        loader: "babel-loader",
        options: { presets: ["@babel/env"] }
      },
      {
        test: /\.css$/, // css的loader,注意执行顺序会从右往左
        use: ["style-loader", "css-loader"]
      }
    ]
  },
  resolve: { extensions: ["*", ".js", ".jsx"] },//自动解析确定的扩展
  output: { // 输出
    path: path.resolve(__dirname, "dist/"),
    publicPath: "/dist/",
    filename: "bundle.js"
  },
};

以上为简单的webpack 打包的相关配置,并通过babel支持jsx语法

index.js

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"));

App.jsx

import React from "react";
import "./App.css";

function App() {
  return (
    <div className="App">
      <h1> Hello, World! </h1>
    </div>
  );
}

export default App;

App.css

.App {
  margin: 1rem;
  font-family: Arial, Helvetica, sans-serif;
}

执行

yarn build

可以看到输出为

$ webpack
asset bundle.js 1.01 MiB [compared for emit] (name: main)
runtime modules 937 bytes 4 modules
modules by path ./node_modules/ 988 KiB
  modules by path ./node_modules/scheduler/ 31.8 KiB 4 modules
  modules by path ./node_modules/react/ 70.6 KiB 2 modules
  modules by path ./node_modules/react-dom/ 875 KiB 2 modules
  ./node_modules/object-assign/index.js 2.06 KiB [built] [code generated]
  ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js 6.67 KiB [built] [code generated]
  ./node_modules/css-loader/dist/runtime/api.js 1.57 KiB [built] [code generated]
modules by path ./src/ 1.08 KiB
  modules by path ./src/*.css 694 bytes
    ./src/App.css 324 bytes [built] [code generated]
    ./node_modules/css-loader/dist/cjs.js!./src/App.css 370 bytes [built] [code generated]
  ./src/index.js 181 bytes [built] [code generated]
  ./src/App.jsx 230 bytes [built] [code generated]
webpack 5.24.2 compiled successfully in 1340 ms
✨  Done in 2.30s.

webpack已经给我们把文件build到了配置的文件夹(bundle.js),新的文件目录为:

.
├── dist
│   └── bundle.js
├── public
│   └── index.html
├── src
│   ├── App.css
│   ├── App.jsx
│   └── index.js
├── package.json
├── webpack.config.js
└── yarn.lock
├── .babelrc

用chrome打开index.html文件 image

这样,我们的第一个目标就达到了:利用webpack打包React项目 如果你仔细看看bundle.js文件你会发现:这个文件很大,所有业务相关代码(包括css)和整个引入的react相关代码都被打包在了里面,这肯定不科学~这么点业务代码就有1M的大小,那写了业务并且加入其它包后还得了?而且打包后的js文件还需要我们手动在index.html引入,这......如果查看页面元素,会发现,写的css直接被加在了html的heard的style里面

so,这个时候我们就要做一些别的工作了

  1. 将打包后的js自动加入到index.html中
  2. 不要将依赖打包进入bundle文件,这样会导致bundle文件很大
  3. 我们期望将css也单独提取作为独立文件

工作1:设置 HtmlWebpackPlugin

yarn add html-webpack-plugin -D

删除public文件夹中index.html这段代码,我们利用插件自动插入js

  <script src="../dist/bundle.js"></script>

修改webpack.config.js 使用这个插件,配置template为public/index.html

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


module.exports = {
  entry: "./src/index.js",
  mode: "development",
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /(node_modules|bower_components)/,
        loader: "babel-loader",
        options: { presets: ["@babel/env"] }
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
      }
    ]
  },
  resolve: { extensions: ["*", ".js", ".jsx"] },
  plugins: [
    new HtmlWebpackPlugin({
      title: '管理输出',
      template: './public/index.html'
    }),
  ],
  output: {
    path: path.resolve(__dirname, "dist/"),
    filename: "bundle.js"
  },
};

重新执行build后会发现dist中新生成的index.html已经自动引入了build后的bundle.js

==当然,你这里完全可以不配置template,dist/index.html会自动生成一个index.html但是,index.js需要执行document.getElementById("root")就会找不到元素导致报错==

顺手引入另外一个插件(清理所有上一次构建dist的文件夹内容)

yarn add clean-webpack-plugin -D

修改webpack.config.js

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


module.exports = {
  entry: "./src/index.js",
  mode: "development",
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /(node_modules|bower_components)/,
        loader: "babel-loader",
        options: { presets: ["@babel/env"] }
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
      }
    ]
  },
  resolve: { extensions: ["*", ".js", ".jsx"] },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: '管理输出',
      template: './public/index.html'
    }),
  ],
  output: {
    path: path.resolve(__dirname, "dist/"),
    filename: "bundle.js"
  },
};

工作2:不要将依赖打包进入bundle文件,这样会导致bundle文件很大,这里只需要增加optimization.splitChunks.chunks:true这个配置

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


module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist/"),
    filename: "[name].bundle.js" // 注意,这里做了调整,因为生成的文件多多文件,filename不可能为同名
  },
  mode: "development",
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /(node_modules|bower_components)/,
        loader: "babel-loader",
        options: { presets: ["@babel/env"] }
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
      }
    ]
  },
  resolve: { extensions: ["*", ".js", ".jsx"] },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: '管理输出',
      template: './public/index.html'
    }),
  ],
  optimization: { // 配置splitChunks
    splitChunks: {
      chunks: 'all',
    },
  },
};

执行 yarn build后生成的dist目录文件为

├── dist
│   ├── index.html
│   ├── main.bundle.js
│   └── vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_react-dom_index_js-node_modu-aef681.bundle.js
├── public
│   └── index.html
├── src
│   ├── App.css
│   ├── App.jsx
│   └── index.js
├── package.json
├── webpack.config.js
└── yarn.lock
├── .babelrc

使用 optimization.splitChunks 配置选项之后,现在应该可以看出除了main文件后生成了一个独立的vendors-***.js文件,main中只包含自己的代码和css代码

工作3:我们期望将css也单独提取作为独立文件

yarn add mini-css-extract-plugin -D

修改webpack.config.js

const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');


module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist/"),
    filename: "[name].bundle.js"
  },
  mode: "development",
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /(node_modules|bower_components)/,
        loader: "babel-loader",
        options: { presets: ["@babel/env"] }
      },
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"]
      }
    ]
  },
  resolve: { extensions: ["*", ".js", ".jsx"] },
  plugins: [
    new CleanWebpackPlugin({}),
    new MiniCssExtractPlugin(),
    new HtmlWebpackPlugin({
      title: '管理输出',
      template: './public/index.html'
    }),
  ],
  optimization: {
    splitChunks: {
      chunks: 'all',
    },
  },
};

build后发现dist文件夹会多了一个main.css文件

检测代码变动,自动build

但是这个时候又发现了一个不方便的地方:每次修改代码文件,都需要重新执行一次build,显然,这是无法接受的,我们可以自动检测文件改动,执行build动作 修改package.json的scripts命令:

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

执行

yarn watch

修改代码后会发现,每次保存后webpack都会重新执行一次build操作~~

下一篇中,我们尝试搭建本地开发环境和热更新 从零到一学会webpack 02-搭建开发环境和热更新