手牵手教学webpack4搭建react App boilerplate

833 阅读7分钟

背景

受到某篇文章的启发 Mistakes Junior React Developers Make

Few junior developers that I have worked with understand how to use Webpack. They work with the codebase that they are provided and just assume everything works. They don’t dive in and figure out how the CSS and ES6 they write are transformed and bundled to end up in the user's browser.

作为一个junior developer,我意识到自己也存在这个问题。

但是 在项目开发中,实际上,我们时常需要解决构建和部署相关的问题。

这些都需要我们对webpack和前端项目的搭建和构建过程有了解,否则通过google去尝试解决问题,只是对问题一知半解,或者可能是碎片的理解问题,下次遇到相似的问题 ,难以举一反三。不如,系统地入门学习一下webpack,有了这些知识基础,下次可以更方便的思考遇到的问题~

webpack的机制

反思自己,经常停留在会用,但是不明白原理的阶段。因此
开始前,先科普几个不得不知的webpack知识点。有助于后面
碰到问题的可以可以在系统的角度上分析~

webpack是什么。我们可以认为是一个模块打包机。从我们指定的入口开始,分析整个项目的文件与文件之间的模块依赖树,通过loader和plugin,解析成浏览器可理解的一个一个静态资源,将构建出一个一个的chunk,输出到output.

如图

简单来说,webpack流程就是, 从一个entry开始,通过import 或者require加载深度优先加载各类文件的依赖,打包。最后输出到output指定的文件夹。

参考下图

webpack的几个概念

  • Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。

  • Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。

  • Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。

  • Loader:模块转换器,用于把模块原内容按照需求转换成新内容。

  • Plugin:扩展插件,在 Webpack 构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要的事情。

  • Output:输出结果,在 Webpack 经过一系列处理并得出最终想要的代码后输出结果

  • webpack plugin和loader的区别

    Loaders:由于webpack只能处理javascript,所以我们需要对一些非js文件处理成webpack能够处理的模块,比如sass文件

    Plugins:Loaders将各类型的文件处理成webpack能够处理的模块,plugins有着很强的能力。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。但也是最复杂的一个。比如对js文件进行压缩优化的UglifyJsPlugin插件

webpack的功能

  • helps you bundle your resources.

  • watches for changes and re-runs the tasks.

  • can run Babel transpilation to ES5, allowing you to use the latest JavaScript features without worrying about browser support.

  • can transpile CoffeeScript to JavaScript

  • can convert inline images to data URIs.

  • allows you to use require() for CSS files.

  • can run a development webserver.

  • can handle hot module replacement.

  • can split the output files into multiple files to avoid having a huge JS file to load in the first page hit.

  • can perform tree shaking.

    总结起来就是,把文件编译成浏览器可识别的代码(eg.ES6->ES5)/代码优化和压缩/开发模式下热更新/更友好的在项目中使用资源依赖

话不多说,让我们先不使用脚手架 从0-1构建个react App吧~🐶
搭建一个react app

创建package.json文件

npm init
安装webpack,webpack cli
(webpack的命令行工具)作为dev dependency
npm i webpack webpack-cli -D

i: install -D: — save-dev

创建一个 src文件夹 在下面创建index.js文件

//index.js
console.log("hello");

在packagejson的script属性中加入两条webpack启动命令

{
  "name": "react_search",
  "version": "1.0.0",
  "description": "Search app using React",
  "main": "index.js",
  "scripts": {
    "start": "webpack --mode development",
    "build": "webpack --mode production"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.0.1",
    "webpack-cli": "^2.0.10"
  }
}

webpack有两种启动模式,developmentproduction。production模式下。构建的代码会被minify.

现在,你的文件结构看起来如图

执行 npm run start

构建效果

执行 npm run build

构建效果

接下来,我们进行 react相关的配置

首先需要安装babel,babel帮助我们把ES6
转成ES5语法。以免有的浏览器不支持。(EG.ie)
npm i react react-dom -S
npm i  babel-loader @babel/preset-react
@babel/core @babel/preset-env -D

上述命令中的@babel/core 和 @babel/preset-env 为babel7所需的依赖,如果是6以下的 请参考官方文档安装 babel-preset-env babel-core babel-preset-react。不同版本的babel安装错误的依赖会导致项目构建报错。

babel-core: Transforms your ES6 code into ES5

babel-loader: Webpack helper to transform your JavaScript 
dependencies (for example, when you import your components
into other components) with Babel

babel-preset-env: Determines which transformations/plugins
to use and polyfills (provide modern functionality on older
browsers that do not natively support it) based on the browser
matrix you want to support

babel-preset-react: Babel preset for all React plugins, 
for example turning JSX into functions

然后,创建一个 webpack.config.js用于存放webpack配置

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      }
    ]
  }
};

babel-loader相关的配置可以放在上述文件(和其他的loader一样),也可以单独放在.babelrc文件中。但我推荐后者 可以增加工程的可读性。

{
  "presets": ["@babel/preset-env","@babel/preset-react"]
}
同样,上述也是babel7的配置内容 babel6 以下长这样
{
  "presets": ["env", "react"]
}

现在我们就可以用react实现我们的页面了

//index.js
import React from "react";
import ReactDOM from "react-dom";

const Index = () => {
  return <div>Hello React!</div>;
};

ReactDOM.render(<Index />, document.getElementById("index"));
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>React and Webpack4</title>
</head>
<body>
  <section id="index"></section>
</body>
</html>

现在我们的文件结构长这样

我们还需要安装html-webpack-plugin并且在webpackconfig中配置。这个插件把index.html作为项目入口,并且将bundle.js 用script标签插入.

npm i html-webpack-plugin -D

现在的webpack配置长这样

const HtmlWebPackPlugin = require("html-webpack-plugin");


module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      }
    ]
  },
  output: {
    path: __dirname + '/dist',
    filename: 'index_bundle.js'
  },
  plugins: [   
    new HtmlWebPackPlugin({
    template: "src/index.html",
    filename: "index.html"
  })]
};

关于html-webpack-plugin的配置项,不清楚的同学可以去查文档~ 运行npm run start后,你的dist文件结构是这样的

跑起来后 就能看到 hello react的字样。

接下来 我们配置webpack的dev server。

dev server是在本地起一个开发服务,可以监听文件的更改,并且热更新

npm i webpack-dev-server -D

修改packagejson文件的script 下的start命令

"start": "webpack-dev-server --mode development --open",

执行npm run start就能通过访问localhost:8080看到我们的页面了

You can also add a --hot flag to your npm start script which will allow you to only reload the component that you’ve changed instead of doing a full page reload. This is Hot Module Replacement.

添加CSS支持

我们需要style-loader 将css inject进DOM元素中。(将css通过style标签插入head中) 和css-loader解析css代码

npm i css-loader style-loader -D

在webpack配置文件中加入css解析配置

const HtmlWebPackPlugin = require("html-webpack-plugin");


module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
      }
    ]
  },
  output: {
    path: __dirname + '/dist',
    filename: 'index_bundle.js'
  },
  plugins: [   
    new HtmlWebPackPlugin({
    template: "src/index.html",
    filename: "index.html"
  })]
};

use字段中,声明的顺序和webpack使用的顺序相反,很显然,我们需要先解析css在插入html中,因此,从左往右的书写顺序如上。

添加css模块化支持

很多时候我们需要将css单独作为一个模块,并且从外部引入,并且不同模块的css之间需要隔离(重名时也不会互相影响),这个需要特殊配置css-loader。

const HtmlWebPackPlugin = require("html-webpack-plugin");


module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },
      {
        test: /\.css$/,
        use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader",
            options: {
              modules: {
                mode: 'local',
                localIdentName: '[path][name]__[local]--[hash:base64:5]',
                hashPrefix: 'heihei',
              },
            }
          }
        ]
      }
    ]
  },
  output: {
    path: __dirname + '/dist',
    filename: 'index_bundle.js'
  },
  plugins: [   
    new HtmlWebPackPlugin({
    template: "src/index.html",
    filename: "index.html"
  })]
};

其中localIdentName指的是编译出来的html元素的class会是什么样子的。加上明明规则后。class的命名就是唯一的了。不需担心在同一个项目中有同名class

[name] will take the name of your component
[local] is the name of your class/id
[hash:base64] is the randomly generated hash which will be unique in every component’s CSS

至此,我们的最简单版模板工程就大功告成了。支持css,css模块化。js和html解析。

如果我们需要使用sass,ts等其他工具时时,需要根据文档加入对应的工具

模板工程地址

webpack资料 webpack配置,如果不需要太多定制的话,其实是可以直接使用的。 但如果需要配置input output publicpath等等,还是十分灵活的。

同时,灵活也会带来一个问题,就是很多新手同学光看配置文档就头晕了。但我后面发现,有问题的时候查文档还是最靠谱的。因为webpack的版本一直在迭代中,各类教程,容易出现过时的情况。

放上webpack相关资料~🐶

webpack

webpack所有的loadder

webpack所有的plugin

总结

webpack是前端开发者的一项基础技能。虽然现在有很多cli工具。可以让我们一件生成工程模板。但是学习和了解webpack都是提高开发效率的必须。

本文的例子是一个很简单的模板工程。目的是为了展示如何配置一个webpack项目。如果有错误,或者解释的不到位的地方,请务必指正~~感激感激

webpack本身的灵活度很高,除了配置项多之外,我们还可以实现自己的loader。实现一些不存在的功能。下篇文章考虑总结一下如何实现一个loader~

参考文章

babel官网

How to Create a React app from scratch using Webpack 4