使用webpack搭建react脚手架

724 阅读3分钟

前言

create-react-app是个优秀的脚手架工具,然而作为前端开发者,不能不会自己搭建脚手架工具。所以,开始了学习webpack之路。

首先,大体了解webpack的工作流程

打包就是将文件打包为另一个文件的能力。webpack将打包过程视为输入、输出两部分,其中这个过程还包括入口、模块、chunk、chunk组合和许多其他部分。从入口开始,webpack会创建一个依赖关系图,这个关系图中包含着应用程序中所需的所有模块,然后打包为bundle。

了解了之后,我们开始正式构建脚手架工具。

  1. 首先进行基础配置,设置入口和出口。 配置文件为webpack.config.js 安装npm install webpack webpack-cli webpack-dev-server. 我这里使用的是webpack4的版本。小伙伴们,请根据需要调整。
const path = require('path')
module.exports = {
    entry: 'src/index.js', // 入口
    output: {
        path: path.resolve(__dirname, 'dist'), // 输出目录
        publicPath: '/dist/', // 加载资源的目录
        filename: 'bundle.js' // 输出文件名
    }
}

我们的目录结构为:

--public
----index.html
----logo.jpeg
--src
-----index.js
-----App.jsx
-----App.css
--webpack.config.js
--.babelrc
  1. 配置webpack使其能打包css、jpeg和jsx文件。 webpack默认是只能打包js文件的。我们需要用不同的loader来增强它的打包能力。 使用stley-loader(自动生成style标签)、css-loader来打包css文件。 使用url-loader、file-loader打包图片资源文件。 同时为了使用es6等语法,使用babel-loader打包js、jsx文件。 webpack配置如下:
module.exports = {
  ...,
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      },
      {
        test: /\.(png|jpeg|gif)$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192
            }
          }
        ]
      }
    ]
  },
  resolve: {
      extensions:['*', '.js', '.jsx'] // 设置按顺序解析文件名
  }
}

配置resolve.extensions属性的含义是:

当使用import * from 'src/App'时,会先按照无后缀名匹配,然后App.js,如果找不到就按照App.jsx匹配文件。

babel的配置如下:

{
  "presets": ["@babel/env", "@babel/preset-react"],
  "plugins": [
    "@babel/plugin-proposal-optional-chaining"
  ]
}
  1. 自动生成html文件 每次打包之后都会生成bundle.js文件,如果改变打包路径的话,就需要手动改HTML文件中的script标签的src属性,为了方便,使用html-webpack-plugin自动生成HTML文件。 npm install html-webpack-plugin --save-dev
...
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
    ...
    plugins: [
        new HtmlWebpackPlugin({
            title: 'webpack for react', // 设置title变量,可以在index.html中读取
            template: 'public/index.html' // 模板文件
        })
    ]
}

html-webpack-plugin还有很多有用的配置,比如设置变量等。在这里设置了title变量,在HTML文件中可以使用<%= htmlWebpackPlugin.options.title %>读取这个变量的值。

  1. 使用clean-webpack-plugin清理打包文件。

如果不清理打包文件,那么每次打包构建后的文件夹dist目录中会有很多多余的文件。 不再介绍如何安装插件。

...
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
    //...
    plugins: [
        //...
        new CleanWebpackPlugin()
    ]
}
  1. 设置webpack-dev-server启动本地服务 开发过程中,大部分时间都是在本地调试,webpack-dev-server提供了本地启动服务的方式。
//...
devServer: {
    contentBase: path.join(__dirname, 'dist'), // 设置代理目录
    port: 3000,
    publicPath: 'http://localhost:3000/dist/', // 设置打包文件url,可以直接写/dist/
    hotOnly: true, // 热更新
    compress: true, // 压缩
    open: true, // 自动打开浏览器
    writeToDisk: true, // webpack-dev-server默认是将文件存在内存里,这里设置为存在磁盘中
    proxy: { // 设置代理
      api: 'http://localhost:3000'
      /* 'api': {
        target: 'http://localhost:3000',
        pathRewrite: {
          '^/api': ''
        }
      } */
    }
    // host: '127.0.0.1' // 修改dev server的host
  },
  1. 设置每次webpack-dev-server重新打包时保留index.html文件 这是启动本地服务后,每次webpack-dev-server重新打包,并不会再次生成index.html文件,而clean-webpack-plugin会将原来的html删除掉,所以为clean-webpack-plugin增加配置,使得每次打包后clean时并不删除index.html文件。
module.exports = {
 // ...
 plugins: [
   new CleanWebpackPlugin({
      /* 使得每次webpack-dev-server打包之后,index.html文件仍然保留 */
      cleanStaleWebpackAssets: false
   })
 ]
}
  1. 将css文件提取出来 css-loader会将css打包到js中,然后再由style-loader生成style标签。现在我使用mini-css-extract-plugin将css提取出来到单独的文件中。 总共有两步:
  2. 替换loader
  3. 增加plugin
module.exports = {
 // ...
 module: {
     rules: {
      {
        test: /\.css$/,
        // 将style-loader替换为MiniCssExtractPlugin.loader
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      }
      // ...
     }
 },
 plugins: [
   new MiniCssExtractPlugin({
      // Options similar to the same options in webpackOptions.output
      // both options are optional
      filename: 'css/[name].css',
      chunkFilename: 'css/[id].css'
    })
 ]
}
  1. 配置热更新 模块热替换(HMR - hot module replacement)功能会在应用程序运行过程中,替换、添加或删除模块,而无需重新加载整个页面。 但是,模块热更新绝不能用在生产环境中。 所以,要先判断是否为生产环境,如果是,则启用;否则,不启用。
// 设置运行环境
const mode = process.env.NODE_ENV
const plugins = []
if (mode !== 'production') {
  // 设置热更新插件只在开发模式下启用,HMR决不能用在生产环境中
  const hotModuleReplacementPlugin = new webpack.HotModuleReplacementPlugin()
  plugins.push(hotModuleReplacementPlugin)
}
module.exports = {
    plugins: [
        ..plugins,
        // ...
    ]
}
  1. 启用sourceMap 一般来讲,生产环境和开发环境的考虑的因素不同,所以对于sourceMap的需求也不同。我这里配置的开发环境的sourceMap主要考虑的准确性,生产环境没有配置sourceMap。
//...
module.exports = {
    // ...
    devtool: mode === 'development' ? 'eval-source-map' : 'none'
}
  1. 处理热更新辅助文件 每次热更新都会生成后缀名为.hot-update.js和.hot-update.json的辅助文件。热更新的次数多了之后,这些辅助文件也相应的多了。我们进行配置,只保留最新的辅助文件。
module.exports = {
    output: {
        // ...
        /* 避免HotModuleReplacementPlugin每次都生成不同的描述文件json和补丁文件js */
    hotUpdateChunkFilename: 'hot/hot-update.js',
    hotUpdateMainFilename: 'hot/hot-update.json'
    }
}

至此,简易的脚手架搭建完毕,当然还有很多缺失,比如对字体的处理、相同模块的提取等等,但是骨架已经出来,大家可以根据这个进行简单学习。 git地址

欢迎大家批评指正。