Webpack 5 超详细解读(四)

31.proxy 代理设置

为什么开发阶段需要设置代理,在开发阶段,我们需要请求后端接口,但是一般后端接口地址和我们本地的不在同一个服务中提供,这时进行访问就会存在跨域的问题,所以我们需要对我们的请求进行转啊操作。模拟跨域请求代码如下:

https://api.github.com/users是github提供的公开接口,可正常请求

在React demo中,index.js使用axios进行请求。

import './title'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App.jsx'
import axios from 'axios'

if (module.hot) {
  module.hot.accept(['./title.js'], () => {
    console.log('title.js模块更新')
  })
}

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

axios.get('/api/users').then((res) => {
  console.log(res.data)
})

由于该接口不存在跨域问题,这里默认他会存在这个问题

...
    proxy: {
      // /api/users
      // http://localhost:4000/api/users
      // https://api.github.com/info/users
      // /api/users---> 返回
      '/api': {
        target: 'https://api.github.com',
        pathRewrite: { "^/api": "" },
        changeOrigin: true
      }
    }
...

首先,在devServer配置中添加proxy配置,添加/api标记,当我们本地服务进行接口请求时,通过/api标记会进入下面的配置中。

设置target属性,告诉Webpack-dev-server检测到该标记后,去请求那个路径(target: 'https://api.github.com')。配置过后,我们回到index.js中,修改请求的路径为/api/users,这是发现依然无法请求成功,该接口依然抛出500服务端异常。

这是因为,github提供的接口下,并没有一个名为/api的服务,所以需要对/api接口 进行路径重写,添加pathRewrite配置,将其值配置为pathRewrite: { "^/api": "" },告诉Webpack-dev-server遇到https://api.github.com/api时,自动替换路径为https://api.github.com/

这时我们依然发现无法访问,这又是因为github对我们的请求来源进行校验,他拒绝了我们的请求。需要设置changeOrigin属性,更改host来源,changeOrigin: true

这时就可以正常访问该接口。

32.resolve 模块解析规则

配置模块如何解析。例如,当在 ES2015 中调用 import 'lodash'resolve 选项能够对 Webpack 查找 'lodash' 的方式去做修改(查看模块)。

resolve文档

32.1 什么是 resolve 模块解析

在开发中我们会有各种各样的模块依赖,例如 js 文件、css 文件、vue 文件等,有自己编写的,也有第三方库。resolve 可以让 Webpack 在 require/import 语句中,找到需要解析的模块代码

32.2 配置自动寻找依赖的路径

模块路径:在 resolve.modules 中配置:到时导入下载好的依赖就会去 node_modules 文件夹里找

resolve: {
    module: ["node_modules"], // 到时就会在 node_modules文件夹里面查找依赖包
}

拓展名配置:配置指定文件后就可以不写此文件的扩展名了

resolve: {
    module: ["node_modules"], // 到时就会在 node_modules文件夹里面查找依赖包
    extendsions: [".js", ".json", ".mjs", "vue"], // 添加了 vue 后导入 vue 文件就不需要加文件扩展名了
  },

配置路径别名:为了简化相对路径的书写,我们直接配置路径给它一个别名:alias

我们需要在 index.js 导入 js 里面的 api.js 时,我们需要写成 ./js/api.js

我们希望写成 js/api ,配置如下:

 resolve: {
    modules: ["node_modules"], // 到时就会在 node_modules文件夹里面查找依赖包
    extensions: [".js", ".json", ".mjs", "vue"], // 添加了 vue 后导入 vue 文件就不需要加文件扩展名了
    alias: {
      "js": path.resolve(__dirname, "./js"), // '以后可以使用 js 代替 ./js
      "@": path.resolve(__dirname, "./src"), // @ 替换根目录
    },
  },

32.3 不同环境下的 Webpack 配置文件

我们在不同的环境下需要不同的配置,显然一个 Webpack.config.js 配置文件是不够的,在不同的环境使用不同的配置,比如我们在生产环境不需要 clearn-Webpack-plugin 的插件清理旧文件。

我们在根目录下新键文件夹:config,在里面新建 三个文件,一个是公共的配置、一个开发环境、一个生产环境。

配置:

先把 Webpack.config.js 文件内容复制一份去 comm.config.js 里, 然后按需提取至不同的配置文件。

把共同的文件留在 comm.config.js 里,使用插件再去各自合并

merge 插件:

安装:npm install Webpack-merge -D

使用:将公共配置和开发环境的配置结合在一起

const { merge } = require("Webpack-merge");
 
const commconfig = require("./Webpack.comm.config");
 
module.exports = merge(commconfig, {
  开发环境的配置
});

注意相对路径的变化,除了有些路径的会默认根目录查找,其他正常路径需要修改。

完整代码演示:

33.source-map 作用

js变异之后生成的具体源码,然后再找回到编译之前的源代码的source-map操作。

代码目录如下:

顺带一提mode

33.1 模式(Mode)

提供 mode 配置选项,告知 Webpack 使用相应模式的内置优化。

string = 'production': 'none' | 'development' | 'production'
用法

只需在配置对象中提供 mode 选项:

module.exports = {
  mode: 'development',
};

或者从 CLI 参数中传递:

Webpack --mode=development

支持以下字符串值:

选项描述
development会将 DefinePluginprocess.env.NODE_ENV 的值设置为 development. 为模块和 chunk 启用有效的名。
production会将 DefinePluginprocess.env.NODE_ENV 的值设置为 production。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePluginFlagIncludedChunksPluginModuleConcatenationPluginNoEmitOnErrorsPluginTerserPlugin
none不使用任何默认优化选项

如果没有设置,Webpack 会给 mode 的默认值设置为 production

如果 mode 未通过配置或 CLI 赋值,CLI 将使用可能有效的 NODE_ENV 值作为 mode

Mode: development

如果设置为development时,Webpack会自动帮我们加上devtool:'eval',这就是下图红框中的内容。

// Webpack.development.config.js
module.exports = {
  mode: 'development',
};

Mode: production(默认值)
// Webpack.production.config.js
module.exports = {
  mode: 'production',
};

Mode: none
// Webpack.custom.config.js
module.exports = {
  mode: 'none',
};

如果要根据 Webpack.config.js 中的 mode 变量更改打包行为,则必须将配置导出为函数,而不是导出对象:

var config = {
  entry: './app.js',
  //...
};

module.exports = (env, argv) => {
  if (argv.mode === 'development') {
    config.devtool = 'source-map';
  }

  if (argv.mode === 'production') {
    //...
  }

  return config;
};

当我们代码中出现了一些错误,但是我们并未设置source-map配置项,这就导致在浏览器中的报错,我们无法定位到具体报错的位置,如下图:

在配置了source-map的时候,本地进行yarn build打包过后,发现打包结果多了main.js.map文件,同时main.js中的内容也更利于查看。

source-map工作流程

  • 根据源文件中的源代码,生成source-map文件
  • 浏览器开启source-map功能,浏览器基于生成的source-map来进行查找

34.devtool 详细说明

此选项控制是否生成,以及如何生成 source map。

使用 SourceMapDevToolPlugin 进行更细粒度的配置。查看 source-map-loader 来处理已有的 source map。

devtool
string = 'eval'` `false

选择一种 source map 风格来增强调试过程。不同的值会明显影响到构建(build)和重新构建(rebuild)的速度。

Webpack 仓库中包含一个 显示所有 devtool 变体效果的示例。这些例子或许会有助于你理解这些差异之处。

你可以直接使用 SourceMapDevToolPlugin/EvalSourceMapDevToolPlugin 来替代使用 devtool 选项,因为它有更多的选项。切勿同时使用 devtool 选项和 SourceMapDevToolPlugin/EvalSourceMapDevToolPlugin 插件。devtool 选项在内部添加过这些插件,所以你最终将应用两次插件。

devtoolperformanceproductionqualitycomment
(none)build: fastest rebuild: fastestyesbundleRecommended choice for production builds with maximum performance.
evalbuild: fast rebuild: fastestnogeneratedRecommended choice for development builds with maximum performance.
eval-cheap-source-mapbuild: ok rebuild: fastnotransformedTradeoff choice for development builds.
eval-cheap-module-source-mapbuild: slow rebuild: fastnooriginal linesTradeoff choice for development builds.
eval-source-mapbuild: slowest rebuild: oknooriginalRecommended choice for development builds with high quality SourceMaps.
cheap-source-mapbuild: ok rebuild: slownotransformed
cheap-module-source-mapbuild: slow rebuild: slownooriginal lines
source-mapbuild: slowest rebuild: slowestyesoriginalRecommended choice for production builds with high quality SourceMaps.
inline-cheap-source-mapbuild: ok rebuild: slownotransformed
inline-cheap-module-source-mapbuild: slow rebuild: slownooriginal lines
inline-source-mapbuild: slowest rebuild: slowestnooriginalPossible choice when publishing a single file
eval-nosources-cheap-source-mapbuild: ok rebuild: fastnotransformedsource code not included
eval-nosources-cheap-module-source-mapbuild: slow rebuild: fastnooriginal linessource code not included
eval-nosources-source-mapbuild: slowest rebuild: oknooriginalsource code not included
inline-nosources-cheap-source-mapbuild: ok rebuild: slownotransformedsource code not included
inline-nosources-cheap-module-source-mapbuild: slow rebuild: slownooriginal linessource code not included
inline-nosources-source-mapbuild: slowest rebuild: slowestnooriginalsource code not included
nosources-cheap-source-mapbuild: ok rebuild: slownotransformedsource code not included
nosources-cheap-module-source-mapbuild: slow rebuild: slownooriginal linessource code not included
nosources-source-mapbuild: slowest rebuild: slowestyesoriginalsource code not included
hidden-nosources-cheap-source-mapbuild: ok rebuild: slownotransformedno reference, source code not included
hidden-nosources-cheap-module-source-mapbuild: slow rebuild: slownooriginal linesno reference, source code not included
hidden-nosources-source-mapbuild: slowest rebuild: slowestyesoriginalno reference, source code not included
hidden-cheap-source-mapbuild: ok rebuild: slownotransformedno reference
hidden-cheap-module-source-mapbuild: slow rebuild: slownooriginal linesno reference
hidden-source-mapbuild: slowest rebuild: slowestyesoriginalno reference. Possible choice when using SourceMap only for error reporting purposes.
shortcutexplanation
performance: buildHow is the performance of the initial build affected by the devtool setting?
performance: rebuildHow is the performance of the incremental build affected by the devtool setting? Slow devtools might reduce development feedback loop in watch mode. The scale is different compared to the build performance, as one would expect rebuilds to be faster than builds.
productionDoes it make sense to use this devtool for production builds? It’s usually no when the devtool has a negative effect on user experience.
quality: bundledYou will see all generated code of a chunk in a single blob of code. This is the raw output file without any devtooling support
quality: generatedYou will see the generated code, but each module is shown as separate code file in browser devtools.
quality: transformedYou will see generated code after the preprocessing by loaders but before additional Webpack transformations. Only source lines will be mapped and column information will be discarded resp. not generated. This prevents setting breakpoints in the middle of lines which doesn’t work together with minimizer.
quality: original linesYou will see the original code that you wrote, assuming all loaders support SourceMapping. Only source lines will be mapped and column information will be discarded resp. not generated. This prevents setting breakpoints in the middle of lines which doesn’t work together with minimizer.
quality: originalYou will see the original code that you wrote, assuming all loaders support SourceMapping.
eval-* additiongenerate SourceMap per module and attach it via eval. Recommended for development, because of improved rebuild performance. Note that there is a windows defender issue, which causes huge slowdown due to virus scanning.
inline-* additioninline the SourceMap to the original file instead of creating a separate file.
hidden-* additionno reference to the SourceMap added. When SourceMap is not deployed, but should still be generated, e. g. for error reporting purposes.
nosources-* additionsource code is not included in SourceMap. This can be useful when the original files should be referenced (further config options needed).

验证 devtool 名称时, 我们期望使用某种模式, 注意不要混淆 devtool 字符串的顺序, 模式是: [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map.

其中一些值适用于开发环境,一些适用于生产环境。对于开发环境,通常希望更快速的 source map,需要添加到 bundle 中以增加体积为代价,但是对于生产环境,则希望更精准的 source map,需要从 bundle 中分离并独立存在。

查看 output.sourceMapFilename 自定义生成的 source map 的文件名。

品质说明(quality)

打包后的代码 - 将所有生成的代码视为一大块代码。你看不到相互分离的模块。

生成后的代码 - 每个模块相互分离,并用模块名称进行注释。可以看到 Webpack 生成的代码。示例:你会看到类似 var module__Webpack_IMPORTED_MODULE_1__ = __Webpack_require__(42); module__Webpack_IMPORTED_MODULE_1__.a();,而不是 import {test} from "module"; test();

转换过的代码 - 每个模块相互分离,并用模块名称进行注释。可以看到 Webpack 转换前、loader 转译后的代码。示例:你会看到类似 import {test} from "module"; var A = function(_test) { ... }(test);,而不是 import {test} from "module"; class A extends test {}

原始源代码 - 每个模块相互分离,并用模块名称进行注释。你会看到转译之前的代码,正如编写它时。这取决于 loader 支持。

无源代码内容 - source map 中不包含源代码内容。浏览器通常会尝试从 web 服务器或文件系统加载源代码。你必须确保正确设置 output.devtoolModuleFilenameTemplate,以匹配源代码的 url。

(仅限行) - source map 被简化为每行一个映射。这通常意味着每个语句只有一个映射(假设你使用这种方式)。这会妨碍你在语句级别上调试执行,也会妨碍你在每行的一些列上设置断点。与压缩后的代码组合后,映射关系是不可能实现的,因为压缩工具通常只会输出一行。

对于开发环境

以下选项非常适合开发环境:

eval - 每个模块都使用 eval() 执行,并且都有 //# sourceURL。此选项会非常快地构建。主要缺点是,由于会映射到转换后的代码,而不是映射到原始代码(没有从 loader 中获取 source map),所以不能正确的显示行数。

eval-source-map - 每个模块使用 eval() 执行,并且 source map 转换为 DataUrl 后添加到 eval() 中。初始化 source map 时比较慢,但是会在重新构建时提供比较快的速度,并且生成实际的文件。行数能够正确映射,因为会映射到原始代码中。它会生成用于开发环境的最佳品质的 source map。

eval-cheap-source-map - 类似 eval-source-map,每个模块使用 eval() 执行。这是 “cheap(低开销)” 的 source map,因为它没有生成列映射(column mapping),只是映射行数。它会忽略源自 loader 的 source map,并且仅显示转译后的代码,就像 eval devtool。

eval-cheap-module-source-map - 类似 eval-cheap-source-map,并且,在这种情况下,源自 loader 的 source map 会得到更好的处理结果。然而,loader source map 会被简化为每行一个映射(mapping)。

特定场景

以下选项对于开发环境和生产环境并不理想。他们是一些特定场景下需要的,例如,针对一些第三方工具。

inline-source-map - source map 转换为 DataUrl 后添加到 bundle 中。

cheap-source-map - 没有列映射(column mapping)的 source map,忽略 loader source map。

inline-cheap-source-map - 类似 cheap-source-map,但是 source map 转换为 DataUrl 后添加到 bundle 中。

cheap-module-source-map - 没有列映射(column mapping)的 source map,将 loader source map 简化为每行一个映射(mapping)。

inline-cheap-module-source-map - 类似 cheap-module-source-map,但是 source mapp 转换为 DataUrl 添加到 bundle 中。

对于生产环境

这些选项通常用于生产环境中:

(none)(省略 devtool 选项) - 不生成 source map。这是一个不错的选择。

source-map - 整个 source map 作为一个单独的文件生成。它为 bundle 添加了一个引用注释,以便开发工具知道在哪里可以找到它。

你应该将你的服务器配置为,不允许普通用户访问 source map 文件!

hidden-source-map - 与 source-map 相同,但不会为 bundle 添加引用注释。如果你只想 source map 映射那些源自错误报告的错误堆栈跟踪信息,但不想为浏览器开发工具暴露你的 source map,这个选项会很有用。

你不应将 source map 文件部署到 web 服务器。而是只将其用于错误报告工具。

nosources-source-map - 创建的 source map 不包含 sourcesContent(源代码内容)。它可以用来映射客户端上的堆栈跟踪,而无须暴露所有的源代码。你可以将 source map 文件部署到 web 服务器。

这仍然会暴露反编译后的文件名和结构,但它不会暴露原始代码。

如果默认的 Webpack minimizer 被覆盖 (例如自定义 terser-Webpack-plugin 选项), 请确保将其替换配置为 sourceMap: true 以启用 SourceMap 支持。

由上面了解到,设置为development后,默认的devtool为eval,这里我们将它更改为source-map

35.ts-loader 编译 TS

在项目开发中我们可能使用TypeScript进行编码开发,所以需要使用WebpackTypeScript进行编译,编译为JavaScript文件。

首先对ts-loader进行安装

yarn add ts-loader

安装完成后,查看项目目录及TypeScript代码:

TypeScript的编译可以使用TypeScript Compiler进行编译:

tsc ./scr/index.ts

编译完成后,在src目录下生成index.js文件。但是这样会存在问题,比如需要编译很多TypeScript文件,或者需要将打包好的文件放到指定的目录。

TypeScript的编译可以使用ts-loader进行编译。

Webpack.config.js配置

const path = require('path')
const { DefinePlugin } = require('Webpack')
const { CleanWebpackPlugin } = require('clean-Webpack-plugin')
const HtmlWebpackPlugin = require('html-Webpack-plugin')

module.exports = {
  mode: 'development',
  entry: './src/index.ts',
  devtool: 'nosources-source-map',
  output: {
    filename: 'js/main.js',
    path: path.resolve(__dirname, 'dist')
  },
  target: 'web',
  devServer: {
    hot: true,
    port: 4000
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: ['babel-loader']
      },
      {
        test: /\.ts$/,
        use: ['ts-loader']
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'copyWebpackPlugin',
      template: './public/index.html'
    }),
    new DefinePlugin({
      BASE_URL: '"./"'
    })
  ]
}

// source-map  cheap-module-source-map

编译完成后,在浏览器中可以正常执行。

36.babel-loader 编译 TS

TypeScript中存在较新的JavaScript代码,比如Promise,可以发现使用ts-loader进行编译没有报错,但是在编译后的文件中,并没有对Promise进行特殊的兼容性处理。所以需要使用babel-loaderTypeScript进行编译。

之前一直在使用@bebel/preset-env,这里我们需要使用@bebel/preset-typescript预设对TypeScript进行编译兼容性处理。

安装@bebel/preset-typescript

yarn add @babel/preset-typescript

在babel.config.js中进行相应配置:

module.exports = {
  presets: [
    ['@babel/preset-env', {
      useBuiltIns: 'usage',
      corejs: 3
    }],
    ['@babel/preset-typescript']
  ]
}

再次打包编译后,发现编译后的main.js代码量大了很多,对Promise等ES6+的语法也做了相应的兼容。

ts-loader和@babel/preset-typescript的区别:

ts-loader虽然不能在编译阶段进行polyfill的填充,但是他可以在编译阶段,提前暴露出来语法错误问题。

反而babel-loader可以进行polyfill填充,但是在编译阶段它不可以进行提前暴露错误,只有在运行阶段时才抛出错误。

如果既想要对TypeScript进行语法转换又想要实时的暴露出错误,TypeScript官网也给出了建议,分先后,在打包之前对语法先做校验,完事之后再做build操作。

37.加载 vue 文件

Webpack.vue文件加载操作:

创建App.vue,并安装vuevue-loadervue-template-compiler

首先,在index.js中导入vue

import Vue from 'vue'
import App from './App.vue'

new Vue({
  render: h => h(App)
}).$mount('#app')

其次在Webpack.config.js中配置相应的loader,由于vue文件中存在less模式的css样式,所以需要单独对css文件进行loader转换。

const path = require('path')
const { DefinePlugin } = require('Webpack')
const { CleanWebpackPlugin } = require('clean-Webpack-plugin')
const HtmlWebpackPlugin = require('html-Webpack-plugin')
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  devtool: false,
  output: {
    filename: 'js/main.js',
    path: path.resolve(__dirname, 'dist')
  },
  target: 'web',
  devServer: {
    hot: true,
    port: 4000
  },
  module: {
    rules: [
      {
        test: /\.less$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2
            }
          },
          'postcss-loader',
          'less-loader'
        ]
      },
      {
        test: /\.jsx?$/,
        use: ['babel-loader']
      },
      {
        test: /\.ts$/,
        use: ['babel-loader']
      },
      {
        test: /\.vue$/,
        use: ['vue-loader']
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'copyWebpackPlugin',
      template: './public/index.html'
    }),
    new DefinePlugin({
      BASE_URL: '"./"'
    }),
    new VueLoaderPlugin()
  ]
}

完成打包编译后,发现页面正常显示,样式文件也生效了。

38.区分打包环境

尝试为不同的工作环境以创建不同的Webpack配置。创建不同的环境配置的方式主要有两种:

  • 第一种是在配置文件中添加相应的配置判断条件,根据环境的判断条件的不同导出不同的配置。

    Webpack配置文件支持导出函数,函数中返回所需要的的配置对象,函数接受两个参数,第一个是env(cli传递的环境名参数),第二个是argv(运行cli过程中传递的所有参数)。可以借助这样一个特点来去实现不同的开发环境和生产环境分别返回不同的配置。

    const Webpack = require('Webpack')
    const { CleanWebpackPlugin } = require('clean-Webpack-plugin')
    const HtmlWebpackPlugin = require('html-Webpack-plugin')
    const CopyWebpackPlugin = require('copy-Webpack-plugin')
    
    module.exports = (env, argv) => {
      const config = {
        mode: 'development',
        entry: './src/main.js',
        output: {
          filename: 'js/bundle.js'
        },
        devtool: 'cheap-eval-module-source-map',
        devServer: {
          hot: true,
          contentBase: 'public'
        },
        module: {
          rules: [
            {
              test: /\.css$/,
              use: [
                'style-loader',
                'css-loader'
              ]
            },
            {
              test: /\.(png|jpe?g|gif)$/,
              use: {
                loader: 'file-loader',
                options: {
                  outputPath: 'img',
                  name: '[name].[ext]'
                }
              }
            }
          ]
        },
        plugins: [
          new HtmlWebpackPlugin({
            title: 'Webpack Tutorial',
            template: './src/index.html'
          }),
          new Webpack.HotModuleReplacementPlugin()
        ]
      }
    
      if (env === 'production') {
        config.mode = 'production'
        config.devtool = false
        config.plugins = [
          ...config.plugins,  // ES6将几个数组组合起来,生产环境下需要clean-Webpack-plugin和copy-Webpack-plugin
          new CleanWebpackPlugin(),
          new CopyWebpackPlugin(['public'])
        ]
      }
      return config
    }
    
    

    命令行运行:yarn Webpack,当没有传递env参数时,Webpack会默认mode为开发阶段(development),对应的public下的文件不会被复制。

    命令行运行:yarn Webpack --env production,传递env参数后,Webpack以生产环境(production)进行打包,额外的插件会工作,public目录下的文件会被复制。

    这就是通过在导出函数中对环境进行判断,从而去实现为不同的环境倒出不同的配置,当然也可以直接在全局去判断环境变量,然后直接导出不同的配置,这样也是可以的。

  • 第二种是为不同的环境单独添加一个配置文件,确保每一个环境下面都会有一个对应的配置文件。

    通过判断环境参数数据返回不同的配对象,这种方式只适用于中小型项目。因为一旦项目变得复杂,配置文件也会一起变得复杂起来,所以说对于大型的项目,还是建议大家使用不同环境去对应不同配置文件的方式来实现。一般在这种方式下面,项目当中至少会有三个Webpack配置文件,其中两个(Webpack.dev.js/Webpack.prod.js)是用来适配不同的环境的,那另外一个是一个公共的配置(Webpack.common.js)。因为开发环境和生产环境并不是所有的配置都完全不同,所以说需要一个公共的文件来去抽象两者之间相同的配置。

    项目目录:

    image-20210106222819337

    Webpack.common.js

    const HtmlWebpackPlugin = require('html-Webpack-plugin')
    
    module.exports = {
      entry: './src/main.js',
      output: {
        filename: 'js/bundle.js'
      },
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              'style-loader',
              'css-loader'
            ]
          },
          {
            test: /\.(png|jpe?g|gif)$/,
            use: {
              loader: 'file-loader',
              options: {
                outputPath: 'img',
                name: '[name].[ext]'
              }
            }
          }
        ]
      },
      plugins: [
        new HtmlWebpackPlugin({
          title: 'Webpack Tutorial',
          template: './src/index.html'
        })
      ]
    }
    
    

    Webpack.dev.js

    const Webpack = require('Webpack')
    const merge = require('Webpack-merge')
    const common = require('./Webpack.common')
    
    module.exports = merge(common, {
      mode: 'development',
      devtool: 'cheap-eval-module-source-map',
      devServer: {
        hot: true,
        contentBase: 'public'
      },
      plugins: [
        new Webpack.HotModuleReplacementPlugin()
      ]
    })
    

    Webpack.prod.js

    const merge = require('Webpack-merge')
    const { CleanWebpackPlugin } = require('clean-Webpack-plugin')
    const CopyWebpackPlugin = require('copy-Webpack-plugin')
    const common = require('./Webpack.common')
    
    module.exports = merge(common, {
      mode: 'production',
      plugins: [
        new CleanWebpackPlugin(),
        new CopyWebpackPlugin(['public'])
      ]
    })
    

    Webpack-merge提供了更加智能的配置合并,使用yarn add Webpack-merge --dev安装到生产环境中。将common中的配置分别于dev和prod组合,生产新的配置。

    命令行运行

    yarn Webpack --config Webpack.prod.js  # --config用于指定配置文件
    # 或者 yarn Webpack --config Webpack.dev.js
    

    如果觉得使用命令行太过麻烦,也可以在package.json进行配置

      "scripts": {
        "prod": "Webpack --config Webpack.prod.js",
        "dev": "Webpack --config Webpack.dev.js"
      },
    

    随后命令行运行

    yarn prod  # 或者yarn dev
    

39.合并生产环境配置

代码目录如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9oGAU04h-1669024600612)(/media/202211/2022-11-16_083157_7965410.4980056499150869.png)]

通过package.json中的scripts命令,指定webpack打包的配置文件(build、build2)。

{
  "name": "02_Webpack_config_start",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "Webpack",
    "serve": "Webpack serve",
    "build2": "Webpack --config ./config/Webpack.common.js --env production",
    "serve2": "Webpack serve --config ./config/Webpack.common.js --env development"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.14.8",
    "@babel/core": "^7.15.0",
    "@babel/plugin-transform-arrow-functions": "^7.14.5",
    "@babel/plugin-transform-block-scoping": "^7.14.5",
    "@babel/preset-env": "^7.15.0",
    "@babel/preset-react": "^7.14.5",
    "@pmmmwh/react-refresh-Webpack-plugin": "^0.4.3",
    "autoprefixer": "^10.3.1",
    "axios": "^0.21.1",
    "babel-loader": "^8.2.2",
    "clean-Webpack-plugin": "^4.0.0-alpha.0",
    "copy-Webpack-plugin": "^9.0.1",
    "css-loader": "^6.2.0",
    "html-Webpack-plugin": "^5.3.2",
    "less": "^4.1.1",
    "less-loader": "^10.0.1",
    "postcss": "^8.3.6",
    "postcss-cli": "^8.3.1",
    "postcss-loader": "^6.1.1",
    "postcss-preset-env": "^6.7.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-refresh": "^0.10.0",
    "react-router-dom": "^5.2.0",
    "style-loader": "^3.2.1",
    "Webpack": "^5.47.1",
    "Webpack-cli": "^4.7.2",
    "Webpack-dev-server": "^3.11.2",
    "Webpack-merge": "^5.8.0"
  },
  "dependencies": {
    "core-js": "^3.16.0",
    "express": "^4.17.1",
    "regenerator-runtime": "^0.13.9",
    "Webpack-dev-middleware": "^5.0.0"
  }
}

paths.js

const path = require('path')

const appDir = process.cwd()

const resolveApp = (relativePath) => {
  return path.resolve(appDir, relativePath)
}

module.exports = resolveApp

Webpack.common.js

const resolveApp = require('./paths')
const HtmlWebpackPlugin = require('html-Webpack-plugin')
const { merge } = require('Webpack-merge')

// 导入其它的配置
const prodConfig = require('./Webpack.prod')
const devConfig = require('./Webpack.dev')

// 定义对象保存 base 配置信息
const commonConfig = {
  entry: './src/index.js',  // 反而没有报错( 相对路径 )
  resolve: {
    extensions: [".js", ".json", '.ts', '.jsx', '.vue'],
    alias: {
      '@': resolveApp('./src')
    }
  },
  output: {
    filename: 'js/main.js',
    path: resolveApp('./dist')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
              esModule: false
            }
          },
          'postcss-loader'
        ]
      },
      {
        test: /\.less$/,
        use: [
          'style-loader',
          'css-loader',
          'postcss-loader',
          'less-loader'
        ]
      },
      {
        test: /\.(png|svg|gif|jpe?g)$/,
        type: 'asset',
        generator: {
          filename: "img/[name].[hash:4][ext]"
        },
        parser: {
          dataUrlCondition: {
            maxSize: 30 * 1024
          }
        }
      },
      {
        test: /\.(ttf|woff2?)$/,
        type: 'asset/resource',
        generator: {
          filename: 'font/[name].[hash:3][ext]'
        }
      },
      {
        test: /\.jsx?$/,
        use: ['babel-loader']
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'copyWebpackPlugin',
      template: './public/index.html'
    })
  ]
}

module.exports = (env) => {
  const isProduction = env.production

  // 依据当前的打包模式来合并配置
  const config = isProduction ? prodConfig : devConfig

  const mergeConfig = merge(commonConfig, config)

  return mergeConfig
}

Webpack.dev.js

const path = require('path')
const CopyWebpackPlugin = require('copy-Webpack-plugin')
const { DefinePlugin } = require('Webpack')
const { CleanWebpackPlugin } = require('clean-Webpack-plugin')
const HtmlWebpackPlugin = require('html-Webpack-plugin')
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-Webpack-plugin')



module.exports = (env) => {
  const isProduction = env.production
  return {
    mode: 'development',
    devtool: false,
    entry: './src/index.js',
    resolve: {
      extensions: [".js", ".json", '.ts', '.jsx', '.vue'],
      alias: {
        '@': path.resolve(__dirname, 'src')
      }
    },
    output: {
      filename: 'js/main.js',
      path: path.resolve(__dirname, 'dist')
    },
    target: 'web',
    devServer: {
      hot: true,
      hotOnly: true,
      port: 4000,
      open: false,
      compress: true,
      historyApiFallback: true,
      proxy: {
        '/api': {
          target: 'https://api.github.com',
          pathRewrite: { "^/api": "" },
          changeOrigin: true
        }
      }
    },
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            'style-loader',
            {
              loader: 'css-loader',
              options: {
                importLoaders: 1,
                esModule: false
              }
            },
            'postcss-loader'
          ]
        },
        {
          test: /\.less$/,
          use: [
            'style-loader',
            'css-loader',
            'postcss-loader',
            'less-loader'
          ]
        },
        {
          test: /\.(png|svg|gif|jpe?g)$/,
          type: 'asset',
          generator: {
            filename: "img/[name].[hash:4][ext]"
          },
          parser: {
            dataUrlCondition: {
              maxSize: 30 * 1024
            }
          }
        },
        {
          test: /\.(ttf|woff2?)$/,
          type: 'asset/resource',
          generator: {
            filename: 'font/[name].[hash:3][ext]'
          }
        },
        {
          test: /\.jsx?$/,
          use: ['babel-loader']
        }
      ]
    },
    plugins: [
      new CleanWebpackPlugin(),
      new HtmlWebpackPlugin({
        title: 'copyWebpackPlugin',
        template: './public/index.html'
      }),
      new DefinePlugin({
        BASE_URL: '"./"'
      }),
      new CopyWebpackPlugin({
        patterns: [
          {
            from: 'public',
            globOptions: {
              ignore: ['**/index.html']
            }
          }
        ]
      }),
      new ReactRefreshWebpackPlugin()
    ]
  }
}

Webpack.prod.js

const CopyWebpackPlugin = require('copy-Webpack-plugin')
const { CleanWebpackPlugin } = require('clean-Webpack-plugin')

module.exports = {
  mode: 'production',
  plugins: [
    new CleanWebpackPlugin(),
    new CopyWebpackPlugin({
      patterns: [
        {
          from: 'public',
          globOptions: {
            ignore: ['**/index.html']
          }
        }
      ]
    })
  ]
}

40.合并开发环境配置

代码目录结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BVLkcq9h-1669024600614)(/media/202211/2022-11-16_083724_5479610.34202323107310295.png)]

同样通过package.json中的scripts指定Webpack配置文件

package.json

{
  "name": "02_Webpack_config_start",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "Webpack",
    "serve": "Webpack serve",
    "build2": "Webpack --config ./config/Webpack.common.js --env production",
    "serve2": "Webpack serve --config ./config/Webpack.common.js --env development"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.14.8",
    "@babel/core": "^7.15.0",
    "@babel/plugin-transform-arrow-functions": "^7.14.5",
    "@babel/plugin-transform-block-scoping": "^7.14.5",
    "@babel/preset-env": "^7.15.0",
    "@babel/preset-react": "^7.14.5",
    "@pmmmwh/react-refresh-Webpack-plugin": "^0.4.3",
    "autoprefixer": "^10.3.1",
    "axios": "^0.21.1",
    "babel-loader": "^8.2.2",
    "clean-Webpack-plugin": "^4.0.0-alpha.0",
    "copy-Webpack-plugin": "^9.0.1",
    "css-loader": "^6.2.0",
    "html-Webpack-plugin": "^5.3.2",
    "less": "^4.1.1",
    "less-loader": "^10.0.1",
    "postcss": "^8.3.6",
    "postcss-cli": "^8.3.1",
    "postcss-loader": "^6.1.1",
    "postcss-preset-env": "^6.7.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-refresh": "^0.10.0",
    "react-router-dom": "^5.2.0",
    "style-loader": "^3.2.1",
    "Webpack": "^5.47.1",
    "Webpack-cli": "^4.7.2",
    "Webpack-dev-server": "^3.11.2",
    "Webpack-merge": "^5.8.0"
  },
  "dependencies": {
    "core-js": "^3.16.0",
    "express": "^4.17.1",
    "regenerator-runtime": "^0.13.9",
    "Webpack-dev-middleware": "^5.0.0"
  }
}

paths.js

const path = require('path')

const appDir = process.cwd()

const resolveApp = (relativePath) => {
  return path.resolve(appDir, relativePath)
}

module.exports = resolveApp

Webpack.common.js

const resolveApp = require('./paths')
const HtmlWebpackPlugin = require('html-Webpack-plugin')
const { merge } = require('Webpack-merge')

// 导入其它的配置
const prodConfig = require('./Webpack.prod')
const devConfig = require('./Webpack.dev')

// 定义对象保存 base 配置信息
const commonConfig = {
  entry: './src/index.js',  // 反而没有报错( 相对路径 )
  resolve: {
    extensions: [".js", ".json", '.ts', '.jsx', '.vue'],
    alias: {
      '@': resolveApp('./src')
    }
  },
  output: {
    filename: 'js/main.js',
    path: resolveApp('./dist')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
              esModule: false
            }
          },
          'postcss-loader'
        ]
      },
      {
        test: /\.less$/,
        use: [
          'style-loader',
          'css-loader',
          'postcss-loader',
          'less-loader'
        ]
      },
      {
        test: /\.(png|svg|gif|jpe?g)$/,
        type: 'asset',
        generator: {
          filename: "img/[name].[hash:4][ext]"
        },
        parser: {
          dataUrlCondition: {
            maxSize: 30 * 1024
          }
        }
      },
      {
        test: /\.(ttf|woff2?)$/,
        type: 'asset/resource',
        generator: {
          filename: 'font/[name].[hash:3][ext]'
        }
      },
      {
        test: /\.jsx?$/,
        use: ['babel-loader']
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'copyWebpackPlugin',
      template: './public/index.html'
    })
  ]
}

module.exports = (env) => {
  const isProduction = env.production

  process.env.NODE_ENV = isProduction ? 'production' : 'development'

  // 依据当前的打包模式来合并配置
  const config = isProduction ? prodConfig : devConfig

  const mergeConfig = merge(commonConfig, config)

  return mergeConfig
}

Webpack.dev.js

const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-Webpack-plugin')

module.exports = {
  mode: 'development',
  devtool: 'cheap-module-source-map',
  target: 'web',
  devServer: {
    hot: true,
    hotOnly: true,
    port: 4000,
    open: false,
    compress: true,
    historyApiFallback: true,
    proxy: {
      '/api': {
        target: 'https://api.github.com',
        pathRewrite: { "^/api": "" },
        changeOrigin: true
      }
    }
  },
  plugins: [
    new ReactRefreshWebpackPlugin()
  ]
}

Webpack.prod.js

const CopyWebpackPlugin = require('copy-Webpack-plugin')
const { CleanWebpackPlugin } = require('clean-Webpack-plugin')

module.exports = {
  mode: 'production',
  plugins: [
    new CleanWebpackPlugin(),
    new CopyWebpackPlugin({
      patterns: [
        {
          from: 'public',
          globOptions: {
            ignore: ['**/index.html']
          }
        }
      ]
    })
  ]
}

babel.config.js

const presets = [
  ['@babel/preset-env'],
  ['@babel/preset-react'],
]

const plugins = []

console.log(process.env.NODE_ENV, '<------')

// 依据当前的打包模式来决定plugins 的值 
const isProduction = process.env.NODE_ENV === 'production'
if (!isProduction) {
  plugins.push(['react-refresh/babel'])
}

module.exports = {
  presets,
  plugins
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

5coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值