webpack包教不包会(一)搭建一个react项目

1,578 阅读4分钟

前言

webpack作为最流行的打包工具,是前端一大利器,主流的开发框架vue/react,也分别在自己的官方脚手架vue-cli/react-create-app中集成了webpack,虽然使用脚手架可以快速实现项目开发,但是内部隐藏了配置的细节,不能方便的实现个性化配置,而且学习webpack也是非常必要的。

其官网的首页图很形象的画出了 Webpack 是什么,如下:

image.png

为了方便阅读和由浅入深学习webpack,我将这个系列分为几篇

开发需求

本篇主要目标是配置一个可以使用的react工作环境,支持:代码保存自动刷新、javascript代码自动兼容问题、一键压缩代码,静态资源处理、问题调试定位、支持less样式...

核心配置

入口(entry)

entry是配置模块的入口,可抽象成输入,Webpack 执行构建的第一步将从入口开始搜寻及递归解析出所有入口依赖的模块。 entry 配置是必填的,若不填则将导致 Webpack 报错退出。

module.exports = {
  entry: './path/to/my/entry/file.js',
};

输出(output)

output属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。

output: {
  path: path.resolve(__dirname, 'dist'), // 配置输出文件存放在本地的目录,必须是 string 类型的绝对路径
  filename: 'my-first-webpack.bundle.js', // 文件名
  publicPath: '/', // output.publicPath 配置发布到线上资源的 URL 前缀,为string 类型。 默认值是空字符串 '',即使用相对路径。
  clean: true, //自动清除之前的dist包
}

Loader

Loader可以看作具有文件转换功能的翻译员,在重新执行Webpack构建前要先安装新引入的 Loader,想知道 Loader 具体支持哪些属性,则需要我们查阅文档,例如 css-loader 还有很多用法,我们可以在 css-loader 主页 上查到。常用loader

  • use 属性的值需要是一个由 Loader 名称组成的数组,Loader 的执行顺序是由后到前的;
  • 每一个 Loader 都可以通过 URL querystring 的方式传入参数,例如 css-loader?minimize 中的 minimize 告诉 css-loader 要开启 CSS 压缩。
 module: {
    rules: [
      {
        // 用正则去匹配要用该 loader 转换的 CSS 文件
        test: /\.css$/,
        use: ['style-loader', 'css-loader?minimize'],
      }
    ]
  }

plugin

Plugin 是用来扩展 Webpack 功能的,通过在构建流程里注入钩子实现,它给 Webpack 带来了很大的灵活性。插件目的在于解决 loader 无法实现的其他事,使用时需要安装插件。常用plugin

这里展示一个自动生成html的插件 html-webpack-plugin

plugins: [
  new HtmlWebpackPlugin({
    title: '搭建一个react项目',
  }),
]

基本安装

创建目录,初始化npm,本地安装webpack,下载需要的loader和plugin

  • webpack-dev-server(提供 HTTP 服务)
  • html-webpack-plugin(自动生成dist下html)
  • css-loader、style-loader(支持css)
mkdir webpack-demo
cd webpack-demo
npm init -y
npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev css-loader style-loader
 webpack-demo
  |- package.json
  |- webpack.config.js
 +|- /src
 +  |- index.js
 +  |- index.css
 +  |- 一张图片.png

index.js文件,显示文本,引入样式

import './index.css'
const div = document.createElement('div')

// 写入文本,添加类名
div.innerHTML = "hello, webpack";
div.classList.add('hello');

document.body.appendChild(div)

css内容,使用背景图片

.hello {
  width: 200px;
  height: 200px;
  color: red;
  background: url('./girl.jpg')
}

配置webpack.config.js

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

module.exports = {
  mode: 'development', // 开发模式
  // JavaScript 执行入口文件
  entry: {
    index: './src/index.js',
  },
  devtool: 'inline-source-map', // 支持 Source Map,以方便调试,此配置仅用于示例,不要用于生产环境
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack包教不包会(一)',
    }),
  ],
  module: {
    rules: [
      {
        // 用正则去匹配要用该 loader 转换的 CSS 文件
        test: /\.css$/,
        use:['style-loader', 'css-loader']
      },
      {
        test: /\.png|svg|jpg|jpeg|gif$/, // 支持使用图片
        type: 'asset/resource'
      }
    ]
  },
  output: {
    // 把所有依赖的模块合并输出到一个 bundle.js 文件
    filename: 'bundle.js',
    // 把输出文件都放到 dist 目录下
    path: path.resolve(__dirname, './dist'),
    clean: true, // 每次更新前自动删除dist文件下内容
  },
};

package.json 使用webpack-dev-server启动本地服务

"scripts": {
    "start": "webpack serve --open", // open启动时自动开启页面
  },

控制台执行npm run start,查看效果

image.png

将index.js故意修改出错

import './index.css'
const div = document.createElement('div')
// 写入文本,添加类名
div.innerHTML = "hello, webpack";
div.classList.ad('hello'); // 这里出错

document.body.appendChild(div)

查看控制台

image.png

帮我们自动定位到出错行代码,方便调试, devtool: 'inline-source-map'生效

到这里,我们已经实现了一个webpack项目,支持提供 HTTP 服务而不是使用本地文件预览;监听文件的变化并自动刷新网页,做到实时预览;支持 Source Map,以方便调试,支持css和图片引入,更详细的步骤可以参考webpack起步

使用react

安装babel

npm i babel-core babel-loader babel-preset-env babel-preset-react babel-preset-stage-0 less-loader -D

  • babel-loader: babel加载器
  • babel-preset-env : 根据配置的 env 只编译那些还不支持的特性。
  • babel-preset-react: jsx 转换成js
  • less-loader less转换器

添加.babelrc配置文件

{
  "presets": ["env", "stage-0","react"] //从左向右解析
}

src新增index.html作为模版文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>webpack包教不包会(一)</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

index.js 写点react

import React, { Component } from 'react';
import { render } from 'react-dom'
import './index.less'

function Hello(params) {
  return <div className="hello">Hello, webpack</div>
}


render(<Hello />, document.getElementById('root'))

修改webpack.config.js

html添加模版文件,js文件使用babel-loader,less使用less-loader

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

module.exports = (env) => {
  // JavaScript 执行入口文件
  return {
    mode: 'development',
    entry: {
      index: './src/index.js',
    },
    // devtool: 'inline-source-map',
    devServer:{
      contentBase: "./dist",//本地服务器所加载的页面所在的目录
    },
    plugins: [
      new HtmlWebpackPlugin({
        title: 'webpack包教不包会(一)',
        template: './src/index.html' // 增加模版入口
      }),
    ],
    module: {
      rules: [
        {
          test:/\.js$/, // 匹配js 使用babel-loader
          use:{
              loader:'babel-loader'
          }
        },
        {
          // 用正则去匹配要用该loader转换的less文件
          test: /\.css$/,
          use:['style-loader', 'css-loader', 'less-loader']
        },
        {
          test: /\.png|svg|jpg|jpeg|gif$/,
          type: 'asset/resource'
        },
    },
    output: {
      // 把所有依赖的模块合并输出到一个 bundle.js 文件
      filename: 'bundle.js',
      // 把输出文件都放到 dist 目录下
      path: path.resolve(__dirname, './dist'),
      clean: true,
    },
  }
};

看下效果

image.png

React已经可以使用了

区分开发环境和生产环境

development(开发环境) 和 production(生产环境) 这两个环境下的构建目标存在着巨大差异。

  • 在开发环境中,我们需要:强大的 source map 和一个有着 live reloading(实时重新加载) 或 hot module replacement(热模块替换) 能力的 localhost server
  • 生产环境目标则转移至其他方面,关注点在于压缩 bundle、更轻量的 source map、资源优化等,通过这些优化方式改善加载时间。 由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置。

拆分webpack.config.js,需要使用webpack-merge,合并config

npm install --save-dev webpack-merge

 webpack-demo
 + |- webpack.common.js
 + |- webpack.dev.js
 + |- webpack.prod.js

webpack.common.js

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

 module.exports = {
    entry: {
      index: './src/index.js',
    },
    plugins: [
      new HtmlWebpackPlugin({
        title: 'webpack包教不包会(一)',
        template: './src/index.html'
      }),
    ],
    module: {
      rules: [
        {
          test: /\.js$/,
          use: 'babel-loader',
          exclude: /node_modules/, // 去掉node_modules 转换
        },
        {
          // 用正则去匹配要用该 loader 转换的 CSS 文件
          test: /\.less$/,
          use:['style-loader', 'css-loader', 'less-loader'],
          exclude: /node_modules/,
        },
        {
          test: /\.png|svg|jpg|jpeg|gif$/,
          type: 'asset/resource'
        },
      ]
    },
   output: {
     filename: 'bundle.js',
     path: path.resolve(__dirname, 'dist'),
     clean: true,
   },
 };

webpack.dev.js

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

 module.exports = merge(common, {
   mode: 'development',
   devtool: 'inline-source-map',
   devServer: {
     contentBase: './dist',
   },
 });

webpack.prod.js

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

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

修改启动命令,区分环境

"scripts": {
   "start": "webpack serve --open --config webpack.dev.js",
   "build": "webpack --config webpack.prod.js"
}

小结

这次我们使用webpack配置了一个基础的react开发项目,并且可以区分开发环境和生产环境配置,这个配置还存在很多可以优化的点,比如分包,压缩,预加载... 将会在下一篇总结webpack的最佳实践。

我们这篇的目标是让大家先从简单开始,手动配置一个基础项目,新手的话会遇到很多报错,大部分可能是包的问题,多操作几次。源码地址

参考资料

wepack中文文档

深入浅出 Webpack