likes
comments
collection
share

从零配置webpack 5 + React脚手架(一)

作者站长头像
站长
· 阅读数 14

前提条件

在开始之前,请确保安装了 Node.js 的最新版本。使用 Node.js 最新的长期支持版本(LTS - Long Term Support),是理想的起步。使用旧版本,你可能遇到各种问题,因为它们可能缺少 webpack 功能以及/或者缺少相关 package 包。

建一个空文件夹

让我们在桌面建一个项目文件夹,名为 my-project ,并使用你的编辑器打开它。 打开终端,快捷键(Ctrl + ~)。

执行以下命令:

npm init -y

上面命令会在 my-project 的根目录生成 package.json 文件,该文件定义了这个项目所需要的各种模块,以及项目的配置信息(比如名称、版本、描述信息等数据)。npm install 命令也是根据这个配置文件,自动下载所需的模块。

安装 webpack

安装最新版本或特定版本,请运行以下命令之一:

npm install --save-dev webpack
npm install --save-dev webpack@<version>

如果你使用 webpack 4+ 版本,你还需要安装 CLI。

npm install --save-dev webpack-cli

安装完成后,package.josn 中多了一个 devDependencies 的属性,这是因为我们安装依赖包时 --save-dev || -D 的结果,这代表了开发时的依赖。 --save || -S 安装依赖包,这代表了运行时依赖。

至于为什么用 npm 安装依赖有 package-lock.json 这个文件,而 yarn 安装依赖为 yarn.lock 在这里不过多赘述下次再聊。

此时我们项目的文件结构如下: project

 my-project
  |- node_modules
  |- package-lock.json
  |- package.json

新建 config 配置文件夹

在根目录下新建一个文件夹名为 config 用于存放配置文件,在此文件夹下创建三个 .js 文件。

  • webpack.common.config.js -- 公共配置文件
  • webpack.prod.config.js -- 生产环境配置文件
  • webpack.dev.config.js -- 开发环境配置文件

在根目录下再新建一个文件夹名为 src ,在其中新建一个.js 文件,名为 app.js 。

project

  my-project
+ |- config
+   |- webpack.common.config.js
+   |- webpack.dev.config.js
+   |- webpack.prod.config.js
  |- node_modules
+ |- src
+   |- app.js
  |- package.json

在 webpack.common.config.js 文件中输入以下代码:

const path = require('path');

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

entry  属性定义了入口文件路径, output  定义了编译打包之后的文件名以及所在路径。这段代码的意思是告诉 webpack,入口文件是  src  目录下的 app.js  文件。打包输出的文件名字为 bundle.js ,bundle.js  文件存放的路径为 dist/js/bundle.js。

那该如何打包呢?在 package.json 中配置如下属性:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
+   "start": "webpack --config ./config/webpack.common.config.js"
  },

运用 --config 修改指定的配置文件,指向./config/webpack.common.config.js

如果我们的文件结构为:

  my-project
  |- node_modules
  |- package.json
  |- webpack.config.js

那么 start 需要修改为:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
+   "start": "webpack"
    或者
+   "start": "webpack --config webpack.config.js"
  },

虽然此时 app.js 中什么代码都没有,但仍然能打包。

现在可以在终端控制台执行以下命令试试:

npm run start

project

  my-project
  |- config
    |- webpack.common.config.js
    |- webpack.dev.config.js
    |- webpack.prod.config.js
+ |- dist
+   |- js
+     |- bundle.js
  |- node_modules
  |- src
    |- app.js
  |- package.json

那么至此,我们已经成功编译打包了一个 js 文件,即入口文件: app.js 。

安装使用 webpack-merge

npm install --save-dev webpack-merge

开发环境(development)和生产环境(production)的构建目标差异很大。在开发环境中,我们需要具有强大的、具有实时重新加载(live reloading)或热模块替换(hot module replacement)能力的 source map 和 localhost server。而在生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置。

虽然,以上我们将生产环境和开发环境做了略微区分,但是,请注意,我们还是会遵循不重复原则(Don't repeat yourself - DRY),保留一个“通用”配置。为了将这些配置合并在一起,我们将使用一个名为 webpack-merge 的工具。通过“通用”配置,我们不必在环境特定(environment-specific)的配置中重复代码。

这就是为什么我一开始在 config 文件夹下创建三个 .js 文件的原因

webpack.prod.config.js

const { merge } = require('webpack-merge');
const common = require('./webpack.common.config.js');

module.exports = merge(common, {
  mode: 'production',
});

在根目录下新建一个文件夹名为: public ,再新建一个 index.html 文件 project

  my-project
  |- config
    |- webpack.common.config.js
    |- webpack.dev.config.js
    |- webpack.prod.config.js
  |- node_modules
+ |- public
+   |- index.html
  |- src
    |- app.js
  |- package.json

public/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>my-project(webpack + react)</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script src="../dist/js/bundle.js"></script>
</html>

回到起初创建的 src/app.js 文件:

let root =document.getElementById('root');
root.innerHTML = 'my-project!!!!!';

package.json

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack --config ./config/webpack.common.config.js",
+   "build": "webpack --config ./config/webpack.prod.config.js"
  },

执行以下代码试试:

npm run build

执行后用浏览器打开 public/index.html 文件看看是不是有“my-project!!!!!”。

安装 React

执行以下命令:

npm install --save react react-dom

安装完成之后,我们就可以写 react 的 JSX 语法了。

src/app.js

import React, { Component } from 'react';

class App extends Component {
  render() {
    return (
      <div>my-project!!!!!</div>
    );
  }
}

export default App;

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';

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

webpack.common.config.js

const path = require('path');

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

ok,现在再执行 npm run bulid 试试。我们会发现 build 打包失败。接着往下解决失败。

安装Babel

因为 webpack 根本识别不了 jsx 语法,所以需要使用 loader 对文件进行预处理。 其中,babel-loader,就是这样一个预处理插件,它加载 ES2015+ 代码,然后使用 Babel 转译为 ES5。那我们开始配置它吧!

首先安装依赖:

npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/plugin-proposal-class-properties @babel/plugin-transform-runtime
  • babel-loader:使用 Babel 和 webpack 来转译 JavaScript 文件。
  • @babel/core:babel 的核心模块
  • @babel/preset-env:转译 ES2015+的语法
  • @babel/preset-react:转译 react 的 JSX
  • @babel/plugin-proposal-class-properties:用来编译类(class)
  • @babel/plugin-transform-runtime:防止污染全局,代码复用和减少打包体积

webpack.common.config.js

const path = require('path');

module.exports = {
  entry: {
    index: './src/index.js',
  },
  output: {
    filename: 'js/bundle.js',
    path: path.resolve(__dirname, '../dist'),
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env', '@babel/preset-react'],
            plugins: [
              '@babel/plugin-transform-runtime',
              '@babel/plugin-proposal-class-properties',
            ],
          },
        },
      },
    ],
  },
};

好的,至此我们就能用 jsx 语法编写代码了。打包看看,是不是成功了。

安装HtmlWebpackPlugin

npm install --save-dev html-webpack-plugin

webpack.prod.config.js

const { merge } = require('webpack-merge');
const common = require('./webpack.common.config.js');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = merge(common, {
  mode: 'production',
  plugins: [
    new HtmlWebpackPlugin({
      template: 'public/index.html',
      filename: 'index.html',
      inject: 'body',
      minify: {
        removeComments: true,
      },
    }),
  ],
});

  • template:基于我们自己定义的 html 文件为模板生成 html 文件
  • filename:打包之后的 html 文件名字
  • inject:将 js 文件注入到 body 最底部
  • minify:压缩 html 文件时的配置
    • removeComments:去除注释

public/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>my-project(webpack + react)</title>
  </head>
  <body>
    <!-- 跟标签 -->
    <div id="root"></div>
  </body>
</html>

现在执行 npm run build 打包时,dist 文件夹中就多出来 index.html 文件,因此自动编译 html 完成,打开看看效果。

解决浏览器缓存,给打包出的 js 文件换个不确定名字

为什么需要换 js 文件名字呐???防止浏览器缓存机制带来的业务代码不更新问题。 浏览器打开 html 页面后,会去请求对应的 js 文件。当客户端请求 js 文件的时候发现名字是一样的,那么它很有可能不发新的 js 包,直接读取了缓存包;如果更换名字后在缓存区中寻找不到,就会去远端服务器中拉取新的 js 资源从而读取到最新的代码。

怎么换不确定名字呐。安排!!! webpack.prod.config.js

module.exports = merge(common, {
  output: {
    filename: 'js/[name]-bundle-[hash:6].js',
  },
});
  • [name] 打包入口名称
  • [hash] 哈希串默认长度为 20,:6 为取前六位

执行 npm run build 打包看看 js 文件是不是不一样了。

现在让我们在 src/app.js 文件中随便在写点东西后再次打包,你会发现 dist/js 的文件夹中又多了个.js,之前的 js 文件并没有清理掉。

打包编译前清理 dist 目录

安装 clean-webpack-plugin

npm install --save-dev clean-webpack-plugin

webpack.prod.config.js

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

module.exports = merge(common, {
  plugins: [
    new CleanWebpackPlugin(),
  ],
 });

现在执行 npm run build ,再检查 dist/js 文件夹。你现在应该不会再看到旧的文件,只有构建后生成的文件!

相信在这肯定有人发现了,每次看效果都非常麻烦。ok 接下来就配置一下webpack-dev-server自动编译打包

安装 webpack-dev-server

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

webpack.dev.config.js

const path = require('path');
const { merge } = require('webpack-merge');
const common = require('./webpack.common.config.js');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = merge(common, {
  mode: 'development',
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    port: 9000,
    compress: true,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'public/index.html',
      inject: 'body',
      hash: false,
    }),
  ],
});

  • contentBase: 启服务的文件
  • port: 端口号
  • compress: 为每个静态文件开启gzip压缩

package.json

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
-   "start": "webpack --config ./config/webpack.common.config.js",
+   "start": "webpack serve --open --config ./config/webpack.dev.config.js",
    "build": "webpack --config ./config/webpack.prod.config.js"
  },

如果不出意外的话执行npm run start就能正常启动本地端口 9000 并且自动在默认浏览器中打开页面。

当你在 app.js 中随意加点文案,回到浏览器中打开的页面都会自动更新了,至此自动编译打包完成。