关中人在岭南 發表於 2022-8-28 17:22:00

从零开始搭建react基础开发环境(基于webpack5)

<h2 id="前言">前言</h2>
<p>最近利用闲暇时间把webpack系统的学习了下,搭建出一个react环境的脚手架,写篇文章总结一下,帮助正在学习webpack小伙伴们,如有写的不对的地方或还有可以优化的地方,望大佬们指出,及时改正。</p>
<blockquote>
<p>git项目地址:https://github.com/handsomezyw/my-webpack</p>
</blockquote>
<h2 id="初始化项目">初始化项目</h2>
<ol>
<li>新建文件夹起名为my-webpack(文件夹名字任意取),然后初始化项目,使用如下指令,会在你的项目根目录生成package.json文件,-y 参数是省去填写初始化的package.json信息。</li>
</ol>
<pre><code class="language-shell">npm init -y
</code></pre>
<p>接着安装webpack、webpack-cli、webpack-dev-server</p>
<pre><code class="language-shell">npm install webpack webpack-cli webpack-dev-server --save-dev
</code></pre>
<ol start="2">
<li>创建<code>src</code>文件夹存放代码等文件,在src文件夹下创建webpack入口文件<code>app.tsx</code>。</li>
<li>创建<code>config</code>文件夹用来保存我们的webpack配置文件,方便扩展管理,使用<code>webpack-merge</code>库将我们写的配置文件组合起来(记得用npm安装webpack-merge)。</li>
</ol>
<ul>
<li>创建基础配置文件 <code>webpack.base.js</code></li>
<li>创建开发环境配置文件 <code>webpack.dev.js</code></li>
<li>创建生产环境配置文件 <code>webpack.prod.js</code></li>
</ul>
<p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d0b2d38d07e842169f7aafdb7f6ba50c~tplv-k3u1fbpfcp-watermark.image?"></p>
<h2 id="配置文件编写">配置文件编写</h2>
<h3 id="webpackbasejs文件基础配置">webpack.base.js文件(基础配置)</h3>
<pre><code class="language-js">const path = require("path");

// 当前项目工作目录根路径
const RootProject = process.cwd();
// 根据参数获取需要的绝对路径
const getPath = (pathStr) =&gt; {
return path.resolve(RootProject, `${pathStr}`);
};

module.exports = {
// 入口文件路径
entry:getPath("./src/app.tsx"),
// 使用插件
plugins: [],
module: {
    // 对模块(module)应用 loader
    rules: [],
},
};
</code></pre>
<h3 id="webpackdevjs文件">webpack.dev.js文件</h3>
<pre><code class="language-js">const path = require("path");
// 合并文件配置
const { merge } = require("webpack-merge");
// 基础配置
const baseConfig = require("./webpack.base");
// 当前项目工作目录根路径
const RootProject = process.cwd();
// 根据参数获取需要的绝对路径
const getPath = (pathStr) =&gt; {
return path.resolve(RootProject, `${pathStr}`);
};

// 开发环境配置
const devConfig = {
// 开发模式
mode: "development",
// 只在发生错误时输出
stats: "errors-only",
// source map风格
devtool: "inline-source-map",
// webpack-dev-server 配置
devServer: {
    // 从目录提供静态文件的选项(默认是 'public' 文件夹)
    static: getPath("./public"),
    // 启用 webpack 的 热模块替换 特性
    hot: true,
        // 监听文件变化
    watchFiles: "src/**/*",
    // 当使用HTML5 History API时,index.html页面可能要代替404响应。
    // 解决了使用react-router-dom 使用 BrowserRouter 模式时,在
    // 浏览器输入路由地址是会请求接口报错的问题
    historyApiFallback: true,
},
};

module.exports = merge(baseConfig, devConfig);
</code></pre>
<h3 id="webpackprodjs文件">webpack.prod.js文件</h3>
<pre><code class="language-js">const path = require("path");
// 合并文件配置
const { merge } = require("webpack-merge");
// 基础配置
const baseConfig = require("./webpack.base");
// 当前项目工作目录根路径
const RootProject = process.cwd();
// 根据参数获取需要的绝对路径
const getPath = (pathStr) =&gt; {
return path.resolve(RootProject, `${pathStr}`);
};

// 生产环境配置
const prodConfig = {
// 生产模式
mode: "production",
// 打包输出配置
output: {
    // 打包输出路径
    path: getPath("./dist"),
    // 打包输出文件名
    filename: "_.js",
    // 开启打包清理文件夹功能
    clean: true,
},
// 使用插件
plugins: [],
};

module.exports = merge(baseConfig, prodConfig);
</code></pre>
<p>接着我们在package.json文件添加运行指令</p>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f5df592a92514f8dbca1011f069e7514~tplv-k3u1fbpfcp-watermark.image?"></p>
<p>--config参数是指定运行使用的配置文件路径</p>
<p>当我们在命令行使用<code>npm run start</code>命令的时候就会运行webpack-dev-server开启 web server服务,并具有实时重新加载的功能,当我们修改文件时,不需要手动刷新浏览器,就能看到修改后的效果。</p>
<p>当我们在命令行使用<code>npm run start</code>命令的时候就会运行webpack构建打包我们的项目</p>
<p>至此webpack的基础配置结构就写好了,接下来就是开始完善配置文件功能。</p>
<h2 id="使用html-webpack-plugin">使用html-webpack-plugin</h2>
<p>这个插件可以帮我们生成一个html文件,在 body 中使用&nbsp;<code>script</code>&nbsp;标签引入你所有 webpack 生成的 bundle,也即是我们output输出的文件。这样就不需要每次手动创建一个html文件,在手动引入我们打包好的js文件。<br>
安装依赖</p>
<pre><code class="language-shell">npm install --save-dev html-webpack-plugin
</code></pre>
<p>在我们的webpack.base.js加入使用</p>
<pre><code class="language-js">// webpack.base.js
// 生成一个 HTML5 文件, 在 body 中使用 script 标签引入你所有 webpack 生成的 bundle
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
    new HtmlWebpackPlugin({
      // 模版文件
      template: getPath("./public/index.html"),
      filename: "index.html",
      inject: true,
      minify: {
      html5: true, // 根据HTML5规范解析输入
      collapseWhitespace: true, // 折叠构成文档树中文本节点的空白
      preserveLineBreaks: false, // 当标签之间的空格包含换行符时,总是折叠为1个换行符(永远不要完全删除它)。必须与collapseWhitespace=true一起使用
      minifyCSS: true, // 在样式元素和样式属性中缩小CSS(使用clean-css)
      minifyJS: true, // 在脚本元素和事件属性中最小化JavaScript(使用Terser)
      removeComments: false, // 带HTML注释
      },
    }),
}
</code></pre>
<h2 id="解析jsx语法">解析jsx语法</h2>
<ul>
<li>将es2015+的语法代码转换为向后兼容的 JavaScript 语法</li>
<li>解析jsx语法</li>
</ul>
<blockquote>
<p>为了提升webpack构建速度,我们可以在使用loader的时候限定解析的范围,使用include,这里需要注意的是,<br>
一般设置src,但是有时node_modules的第三方依赖打包好的库,可能含有es6的语法,为了更好的兼容性,我们可以把第三方的依赖库通过include加入到解析名单,比如:include:["./src","./node_modules/react-router-dom"],不过觉得麻烦的话,又不在意构建速度的话,可以不限定解析范围。</p>
</blockquote>
<p>这里我们使用babel-loader来帮助我们解析jsx和es2015以后的版本语法。</p>
<p>使用core-js给我们新的js api打补丁(polyfill)。</p>
<p>关于babel可前往babel官网查看更多配置用法。</p>
<p>安装所需依赖</p>
<pre><code class="language-shell">npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/preset-react core-js
</code></pre>
<p>接着在<code>webpack.base.js</code>文件中添加babel-loader解析js,jsx文件</p>
<pre><code class="language-js">// webpack.base.js
//...
module: {
    // 对模块(module)应用 loader
    rules: [
      {
      test: /\.jsx?$/,
      // include限定解析范围
      include: getPath("./src"),
      use: [
          {
            loader: "babel-loader",
            options: {
            presets: [
                [
                   // 解析js预设
                  "@babel/preset-env",
                  {
                  // 自动打补丁
                  useBuiltIns: "usage",
                  // 指定core-js版本
                  corejs: { version: "3.24.1", proposals: true },
                  },
                ],
                // 解析jsx预设
                "@babel/preset-react",
            ],
            plugins: [],
            },
          },
      ],
      },
    ],
},
//...
</code></pre>
<h2 id="解析cssless文件">解析css、less文件</h2>
<ul>
<li>解析css、less</li>
<li>提取css成单独文件</li>
<li>css浏览器前缀补齐</li>
<li>开启css modules</li>
<li>引入全局less变量</li>
</ul>
<p>安装所需依赖</p>
<pre><code class="language-shell">npm install --save-dev css-loader less less-loader mini-css-extract-plugin postcss-loader postcss style-resources-loader postcss-preset-env
</code></pre>
<p>这里需要注意的是css前缀补齐,还需要在<code>package.json</code>文件添加browserlist属性指定版本,不然css补齐前缀不生效。</p>
<p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8b6142c6c4094869a212c3f066a1842d~tplv-k3u1fbpfcp-watermark.image?"></p>
<pre><code class="language-js">//webpack.base.js
// 将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
//...
plugins: [
    new MiniCssExtractPlugin({
      // 提取css文件 文件名
      filename: "_.css",
    }),
]
module: {
    // 对模块(module)应用 loader
    rules: [
       {
      test: /\.css$/,
      use: [
          // "style-loader",
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader",
            options: {
            modules: {
                auto: true,
                localIdentName: "__--",
            },
            importLoaders: 1,
            },
          },
          {
            loader: "postcss-loader",
            options: {
            postcssOptions: {
                plugins: ["postcss-preset-env"],
            },
            },
          },
      ],
      },
      {
      test: /\.less$/,
      use: [
          // "style-loader", //将style插入到head中
          MiniCssExtractPlugin.loader, // 提取css成单独文件
          {
            loader: "css-loader",
            options: {
            modules: {
                auto: true, // 允许根据文件名中含有module的文件开启css modules
                localIdentName: "__--", // 允许配置生成的本地标识符(ident)
            },
            // css-loader之前加载多少个loader
            importLoaders: 3,
            },
          },
          {
            loader: "postcss-loader",
            options: {
            postcssOptions: {
                // 包含添加css前缀的预设
                plugins: ["postcss-preset-env"],
            },
            },
          },
          {
            loader: "less-loader",
            options: {
            // 生成source map
            sourceMap: true,
            },
          },
          {
            // 将定义全局的less变量注入到其他样式文件,无须手动引入
            loader: "style-resources-loader",
            options: {
            patterns: ,
            },
          },
      ],
      },
    ],
},
//...
</code></pre>
<h2 id="解析图片资源">解析图片资源</h2>
<pre><code class="language-js">// webpack.base.js
//...
module:{
    rules: [
      {
      test: /\.(png|jpg|gif|jpeg)$/,
      type: "asset/resource",
      },
    ]
}
//...
</code></pre>
<h2 id="解析字体资源">解析字体资源</h2>
<pre><code class="language-js">// webpack.base.js
//...
module:{
    rules: [
      {
      test: /\.(woff|woff2|eot|ttf|otf)$/,
      type: "asset/resource",
      },
    ]
}
//...
</code></pre>
<h2 id="解析tstsx">解析ts、tsx</h2>
<p>安装所需依赖</p>
<pre><code class="language-shell">npm install --save-dev typescript ts-loader fork-ts-checker-webpack-plugin
</code></pre>
<p>安装完依赖后,初始化ts生成ts配置文件<code>tsconfig.json</code>,使用如下指令(确保当前命令行所处位置为项目根目录):</p>
<pre><code class="language-shell"> node_modules/.bin/tsc --init
</code></pre>
<p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a714cb8898cd478180197f605e19fd6a~tplv-k3u1fbpfcp-watermark.image?"><br>
修改tsconfig.json文件的配置(关于配置可前往ts官网查询)</p>
<pre><code class="language-json">{
    "compilerOptions": {
      "target": "ES5",
      "module": "ES6",
      "jsx": "react",
      "paths": {
          "@/*": ["./src/*"]
      },
      "sourceMap": true,
    }
}
</code></pre>
<pre><code class="language-js">// webpack.base.js
// 在一个独立进程上运行TypeScript类型检查器的Webpack插件。
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
//...
plugins: [
    new ForkTsCheckerWebpackPlugin()
],
module:{
    rules: [
      {
      test: /\.tsx?$/,
      use: "ts-loader",
      include: getPath("./src"),
      },
    ]
}
//...
</code></pre>
<h2 id="命令行显示的信息优化">命令行显示的信息优化</h2>
<p>通常我们运行项目时都会显示很多构建信息</p>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7c9716aa4392468f8b4d1c7c9f1cb812~tplv-k3u1fbpfcp-watermark.image?"></p>
<p>每次重新编译都会有一大堆信息,显然不利于我们专注研发,为此我们可以通过stats设置显示信息的风格。</p>
<p>在使用一个插件优化显示命令行显示。</p>
<pre><code class="language-shell">npm install --save-dev @soda/friendly-errors-webpack-plugin
</code></pre>
<pre><code class="language-js">// webpack.base.js
// 命令行提示优化插件
const FriendlyErrorsWebpackPlugin = require("@soda/friendly-errors-webpack-plugin");
module.exports = {
// ...
// 只在发生错误时输出
stats: "errors-only",
plugins:
// ...
}
</code></pre>
<p>效果如下</p>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8ef3253460f44429926163fde9efc195~tplv-k3u1fbpfcp-watermark.image?"></p>
<h2 id="eslint-prettier配置">eslint prettier配置</h2>
<p>使用eslint和prettier帮助我们检查代码格式是否正确和统一代码格式。</p>
<p>vscode安装ESLint、Prettier插件。</p>
<p>这里我们使用airbnb公司的eslint基础规则。</p>
<p>先安装依赖</p>
<pre><code class="language-js">npm install --save-dev eslint-config-airbnb eslint eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y
</code></pre>
<p>然后在创建.eslintrc.js文件,配置我们的eslint规则。配置选项可前往eslint官网查看。</p>
<pre><code class="language-js">module.exports = {
env: {
    node: true, // 启用node中全局变量
    browser: true, // 启用浏览器中全局变量
},
// extends: "eslint:recommended",
extends: ["airbnb", "airbnb/hooks", "plugin:prettier/recommended", "prettier"],

parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: "latest",
    sourceType: "module",
},
settings: {
    "import/resolver": {
      webpack: {
      config: "./config/webpack.base.js",
      },
    },
},
rules: {
    // "off" or 0 - 关闭规则
    // "warn" or 1 - 将规则视为一个警告(不会影响退出码)
    // "error" or 2 - 将规则视为一个错误 (退出码为1)

    // 要求或禁止在类成员之间出现空行
    "lines-between-class-members": ,
    // 允许的扩展集是可配置的 jsx可以出现在js中
    "react/jsx-filename-extension": [
      1,
      {
      extensions: [".js", ".jsx"],
      },
    ],
    // 函数组件声明方式
    "react/function-component-definition": [
      2,
      {
      namedComponents: ["function-declaration", "function-expression", "arrow-function"],
      unnamedComponents: ["function-expression", "arrow-function"],
      },
    ],
    // 禁用 console
    "no-console": "off",
    // 要求 require() 出现在顶层模块作用域中
    "global-require": "off",
    "import/no-extraneous-dependencies": "off",
},
};
</code></pre>
<p>接着安装eslint-webpack-plugin插件,该插件使用&nbsp;<code>eslint</code>&nbsp;来查找和修复 JavaScript 代码中的问题。</p>
<pre><code class="language-shell">npm install eslint-webpack-plugin --save-dev
</code></pre>
<p>接着在我们webpack配置文件中使用eslint-webpack-plugin插件</p>
<pre><code class="language-js">// webpack.base.js
const path = require("path");
// 使用 eslint 来查找和修复 JavaScript 代码中的问题
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
// 当前项目工作目录根路径
const RootProject = process.cwd();
// 根据参数获取需要的绝对路径
const getPath = (pathStr) =&gt; {
return path.resolve(RootProject, `${pathStr}`);
};
module.exports = {
    plugins: [
         new ESLintWebpackPlugin({
          // 指定检查文件的根目录
          context: getPath("./src"),
      }),
    ]
}
</code></pre>
<p>eslint配置完后,在创建.prettierrc.js文件配置Prettier。配置选项可前往Prettier官网查看。</p>
<pre><code class="language-js">module.exports = {
// 一行最多 100 字符
printWidth: 100,
// 关闭 tab 缩进
useTabs: false,
// 使用 2个tab 缩进
tabWidth: 2,
// 行尾需要有分号
semi: true,
// 使用单引号
singleQuote: false,
// 对象key是否使用引号
// as-needed 仅在需要的时候使用
// consistent 有一个属性需要引号,就都需要引号
// preserve 保留用户输入的情况
quoteProps: "as-needed",
// jsx 使用单引号代替双引号
jsxSingleQuote: false,
// 末尾不需要逗号
trailingComma: "all",
// 大括号内的首尾需要空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 箭头函数,只有一个参数的时候,也需要括号 &lt;always|avoid&gt;
arrowParens: "always",
};
</code></pre>
<p>需要注意的是ESLint和Prettier配置可能会产生冲突,这时我们可以安装eslint-config-prettier插件和eslint-plugin-prettier插件,来解决冲突。</p>
<p>eslint-config-prettier插件 关闭所有可能与Prettier冲突的ESlint规则 使用要在eslint配置文件的extends数组最后加"prettier"</p>
<p>eslint-plugin-prettier插件 将Prettier作为ESLint的规则来使用,相当于代码不符合Prettier的规范时,会有提示信息使用要在eslint配置文件的extends数组加入"plugin:prettier/recommended"</p>
<p>安装依赖</p>
<pre><code class="language-shell">npm install --save-dev eslint-config-prettier eslint-plugin-prettier prettier
</code></pre>
<p>这样配置就完成了,如果有什么需要修改的规则可在.eslintrc.js文件中修改rules来修改elslint规则。</p>
<h2 id="单元测试">单元测试</h2>
<p>我们可以写一些单元测试检测我们脚手架基础功能是否有问题,比如是否生成js文件,css文件,index.html。</p>
<p>这里我们使用mocha测试框架来编写我们的测试用例。</p>
<p>安装依赖</p>
<pre><code class="language-shell">npm install --save-dev mocha
</code></pre>
<p>接着在我们项目下创建test文件夹</p>
<p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c2ed4191e1e9468e8ffa55c971d7449a~tplv-k3u1fbpfcp-watermark.image?"></p>
<p>接着在test文件夹下创建index.js文件编写测试用例。</p>
<pre><code class="language-js">// index.js
// 这里我们使用了glob-all库来判断是否有文件(记得安装这个库)
const glob = require("glob-all");

describe("检查是否生成了html文件", () =&gt; {
it("生成html文件", (done) =&gt; {
    const files = glob.sync(["./dist/index.html"]);

    if (files.length &gt; 0) {
      done();
    } else {
      throw new Error("生成html文件失败");
    }
});
});
</code></pre>
<p>然后在package.json文件加入运行测试指令</p>
<p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e19d49289674486e889c94ae4d7cffb5~tplv-k3u1fbpfcp-watermark.image?"></p>
<p>先运行指令npm run build指令打包,成功后在运行npm run test指令</p>
<p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/50947eea17d4456c9f31aa017bc3fa13~tplv-k3u1fbpfcp-watermark.image?"></p>
<p>这样一个简单的测试用例就完成了。</p>
<p>接下来就是查看测试覆盖率,可以使用nyc这个库,有兴趣可以去看看。</p>
<h2 id="构建速度优化">构建速度优化</h2>
<p>随着项目文件的增多,webpack打包和运行项目的速度必然会下降,我们可以优化一下。</p>
<h3 id="限定解析范围includeexclude">限定解析范围(include,exclude)</h3>
<p>我们可以在使用loader的时候,使用include,exclude去控制解析文件的范围,比如只解析src文件夹下的文件include: ["./src"]。</p>
<h3 id="开启多进程构建">开启多进程构建</h3>
<p>使用 thread-loader 来开启多进程构建,提升我们的构建速度。</p>
<p>安装依赖</p>
<pre><code class="language-shell">npm install --save-dev thread-loader
</code></pre>
<p>然后在我们使用的loader前面加入(也就是放在use数组第一位),因为loader的解析是从右往左解析的,所以放在第一个。</p>
<blockquote>
<p>需要注意的是ts-loader配合thread-loader,我们得多一点额外的设置</p>
</blockquote>
<pre><code class="language-js">// 在一个独立进程上运行TypeScript类型检查器的Webpack插件。
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");

module.exports = {
    plugins: [
            new ForkTsCheckerWebpackPlugin({
            typescript: {
                diagnosticOptions: {
                  semantic: true,
                  syntactic: true,
                },
          },
      })
    ],
    module: {
      rules: [
            {
                test: /\.tsx?$/,
                use: [
                  {
                        loader: "ts-loader",
                        options: {
                        happyPackMode: true,
                        },
                  }
                ]
            }
      ]
    }
}
</code></pre>
<pre><code class="language-js">// webpack官网thread-loader的例子
use: [
{
    loader: "thread-loader",
    // 有同样配置的 loader 会共享一个 worker 池
    options: {
      // 产生的 worker 的数量,默认是 (cpu 核心数 - 1),或者,
      // 在 require('os').cpus() 是 undefined 时回退至 1
      workers: 2,

      // 一个 worker 进程中并行执行工作的数量
      // 默认为 20
      workerParallelJobs: 50,

      // 额外的 node.js 参数
      workerNodeArgs: ['--max-old-space-size=1024'],

      // 允许重新生成一个僵死的 work 池
      // 这个过程会降低整体编译速度
      // 并且开发环境应该设置为 false
      poolRespawn: false,

      // 闲置时定时删除 worker 进程
      // 默认为 500(ms)
      // 可以设置为无穷大,这样在监视模式(--watch)下可以保持 worker 持续存在
      poolTimeout: 2000,

      // 池分配给 worker 的工作数量
      // 默认为 200
      // 降低这个数值会降低总体的效率,但是会提升工作分布更均一
      poolParallelJobs: 50,

      // 池的名称
      // 可以修改名称来创建其余选项都一样的池
      name: "my-pool"
    },
},
// 耗时的 loader(例如 babel-loader)
];
</code></pre>
<h3 id="开启多进程压缩">开启多进程压缩</h3>
<p>webpack5默认是使用TerserWebpackPlugin插件来压缩js,可使用多进程并发运行以提高构建速度。<br>
把下面的配置加入到我们的webpack.prod.js配置文件中,开启多进程并发运行压缩。</p>
<pre><code class="language-js">module.exports = {
optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
      // 开启多进程并发运行
      parallel: true,
      }),
    ],
},
};
</code></pre>
<h3 id="预编译资源">预编译资源</h3>
<p>预编译资源就是我们可以把不需要经常变动的第三方依赖,如react,react-dom等,提前打包好,在webpack构建时不用在去编译已经编译好的依赖了,这样就可以大幅提升构建速度。</p>
<p>在webpack中,我们使用<code>DllPlugin</code>&nbsp;和&nbsp;<code>DllReferencePlugin</code>这两个webpack自带的插件,帮助我们完成资源预编译。</p>
<p>首先在我们的config文件夹下创建webpack.dll.js,写入如下代码</p>
<pre><code class="language-js">const path = require("path");
const webpack = require("webpack");

// 当前项目工作目录路径
const RootProject = process.cwd();

module.exports = {
entry: {
    // 根据你的需要添加,这里是把react,react-dom预编译
    library: ["react", "react-dom"],
},
output: {
    filename: ".dll.js",
    path: path.resolve(RootProject, "./library"),
    library: "_",
},
plugins: [
    new webpack.DllPlugin({
      // 与output.library保持一致
      name: "_",
      path: path.resolve(RootProject, "./library/-manifest.json"),
    }),
],
};
</code></pre>
<p>然后在package.json加入运行构建指令</p>
<p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fdaa4b1f7c814887808fb16c4a9dfc18~tplv-k3u1fbpfcp-watermark.image?"></p>
<p>然后我们预编译资源的配置就写好了,接着安装react,react-dom,</p>
<pre><code class="language-shell">npm install react react-dom
</code></pre>
<p>接着在命令行运行<code>npm run dll</code>指令生成预编译资源资源</p>
<p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/146fa775316e4b889634295bf2ae1acb~tplv-k3u1fbpfcp-watermark.image?"></p>
<p>可以看到成功生成了library文件夹及文件夹下的预编译资源library.dll.js,文件夹中json文件是给DllReferencePlugin使用告诉webpack哪些是编译好的资源,不用在编译了。</p>
<p>最后在我们的webpack.prod.js文件中使用DllReferencePlugin。还有帮我们把预编译资源引入到项目中的插件<code>add-asset-html-webpack-plugin</code>(其实就是把我们生成的预编译文件复制一份到webpack的ouput输出目录,并把它通过script标签引入到output输出目录下的index.html文件中)</p>
<pre><code class="language-shell">npm install --save-dev add-asset-html-webpack-plugin
</code></pre>
<pre><code class="language-js">// webpack.prod.js
const webpack = require("webpack");
// 将指定js文件提取至压缩目录,并用script标签引入
const AddAssetHtmlPlugin = require("add-asset-html-webpack-plugin");

module.exports = {
    plugins: [
      new webpack.DllReferencePlugin({
          // 编译时用于加载 JSON manifest 的绝对路径
          manifest: require(getPath("./library/library-manifest.json")),
      }),
      new AddAssetHtmlPlugin({
          filepath: getPath("./library/library.dll.js"),
          publicPath: "./",
      }),
    ]
}
</code></pre>
<p>这样就完成了预解析资源。</p>
<h2 id="构建体积优化">构建体积优化</h2>
<p>随着项目文件的增多,打包出来bundle体积会越来越大,我们可以做些优化。</p>
<h3 id="使用cdn引入第三方库">使用cdn引入第三方库</h3>
<p>防止将某些import的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖。<br>
比如下面的例子,lodash不会被打包到项目中,而是通过cdn的形式引入。</p>
<pre><code class="language-js">// 官方文档例子
module.exports = {
// ...
externalsType: 'script',
externals: {
    lodash: ['https://cdn.jsdelivr.net/npm/lodash@4.17.19/lodash.min.js', '_'],
},
};

// 使用
import _ from 'lodash';
console.log(_.head());
</code></pre>
<h3 id="包体积分析针对优化">包体积分析,针对优化</h3>
<p>我们可以使用<code>webpack-bundle-analyzer</code>插件,分析打包好的文件大小,便于我们分析优化哪个文件过大,针对的去做一些优化。</p>
<p>安装依赖</p>
<pre><code class="language-shell">npm install --save-dev webpack-bundle-analyzer
</code></pre>
<pre><code class="language-js">// webpack.prod.js
// 分析构建体积插件
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");

module.exports = {
    plugins: [
      new BundleAnalyzerPlugin()
    ]
}
</code></pre>
<p>运行完构建效果如下</p>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c017ea10f2f249d698401380914d48d0~tplv-k3u1fbpfcp-watermark.image?"></p>
<h3 id="图片压缩">图片压缩</h3>
<p>图片压缩我们可以使用image-minimizer-webpack-plugin插件(下载可能有点慢,多下几次),这里我们使用无损压缩。</p>
<pre><code class="language-shell">npm install --save-dev image-minimizer-webpack-plugin imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo
</code></pre>
<pre><code class="language-js">// webpack.prod.js
// 图片压缩插件(使用无损压缩配置)
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
module.exports = {
    new ImageMinimizerPlugin({
      minimizer: {
      // implementation: ImageMinimizerPlugin.imageminMinify,
      implementation: ImageMinimizerPlugin.imageminGenerate,
      options: {
          // Lossless optimization with custom option
          // Feel free to experiment with options for better result for you
          plugins: [
            ["gifsicle", { interlaced: true }],
            ["jpegtran", { progressive: true }],
            ["optipng", { optimizationLevel: 5 }],
            // Svgo configuration here https://github.com/svg/svgo#configuration
            [
            "svgo",
            {
                plugins: [
                  {
                  name: "preset-default",
                  params: {
                      overrides: {
                        // customize default plugin options
                        inlineStyles: {
                        onlyMatchedOnce: false,
                        },
                        // or disable plugins
                        removeDoctype: false,
                      },
                  },
                  },
                ],
            },
            ],
          ],
      },
      },
    }),
}
</code></pre>
<h3 id="css压缩">css压缩</h3>
<p>我们可以使用<code>css-minimizer-webpack-plugin</code>这个插件来帮我们压缩css文件。</p>
<blockquote>
<p>需要注意的是,使用这个插件会导致terser-webpack-plugin插件压缩js失效,解决办法是再次引入terser-webpack-plugin</p>
</blockquote>
<p>安装依赖</p>
<pre><code class="language-shell">npm install --save-dev css-minimizer-webpack-plugin
</code></pre>
<pre><code class="language-js">// webpack.prod.js
// 这个插件使用 cssnano 优化和压缩 CSS。
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
// 压缩js插件
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
    optimization: {
      minimize: true,
      minimizer: [
          new CssMinimizerPlugin(),
          new TerserPlugin({
            parallel: 4
          }),
      ],
    }
}
</code></pre>
<h2 id="splitchunksplugin">SplitChunksPlugin</h2>
<p>我们可以通过SplitChunksPlugin来抽取公共资源包,如lodash等,达到减小打包的体积。<br>
使用如下配置,当我们项目中引用到了lodash库的时候,lodash会被单独抽取出来,在名为vendor开头的js文件中。</p>
<pre><code class="language-js">module.exports = {
    splitChunks: {
      cacheGroups: {
      vendor: {
          test: /[\\/]node_modules[\\/](lodash)[\\/]/,
          name: "vendor",
          chunks: "all",
      },
      },
    },
},
}
</code></pre>
<p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/68ad74340de84330bd4017e8cf6a70a2~tplv-k3u1fbpfcp-watermark.image?"></p>
<h2 id="关于自动部署">关于自动部署</h2>
<p>之前买了个腾讯云的服务器,最近打算写写express练练手,但是发现个问题,我打包好的文件怎么弄到服务器上去,然后就去百度了解了下ssh传输文件,前提是你的服务器要安装openssh-server,可以百度一下安装方法,安装好了后,就可以使用sftp(安全文件传输)通过登录密码验证的形式连接你的服务器传输文件。也可以配置密钥连接省去每次输入密码。但是还是有个问题,服务器端我用pm2管理我的项目文件夹,只要每次把新的打包文件重新拷贝一份到服务器端的项目文件夹下,就可以完成项目更新,这导致服务器端的项目更新每次都得通过ssh去把文件传上去,这样才能完成服务器端的项目文件更新,感觉有点麻烦,如果能执行完npm run build后自动把我的文件传到服务端该多省事呀,基于这个想法,查了查文档写了个自动部署的插件<code>@handsomezyw/auto-deploy-webpack-plugin</code>完成这件事。</p>
<p>先安装插件</p>
<pre><code class="language-shell">npm install --save-dev&nbsp;@handsomezyw/auto-deploy-webpack-plugin
</code></pre>
<p>使用方法也很简单</p>
<pre><code class="language-js">// webpack.prod.js
// 自动部署到服务端插件
const AutoDeployWebpackPlugin = require("@handsomezyw/auto-deploy-webpack-plugin");

module.exports = {
    plugins: [
      new AutoDeployWebpackPlugin({
          // 服务器配置
          serverOptions: {
            // 服务器用户名
            username: "administrator",
            // 服务器ip地址
            host: "xxx.xx.x.xxx",
            // 服务器登录密码
            password: "123456",
          },
          // 本地要上传到服务端的文件夹路径
          localPath: "/Users/zengyongwen/Desktop/study/webpack-study/dist",
          // 上传到服务器文件夹路径(这里需要注意的是,会把该文件夹先清理一遍,再上传)
          serverPath: "Desktop/my-system/public",
      }),
    ]
}
</code></pre>
<p>最后我们在安装下react、react-dom的声明文件</p>
<pre><code class="language-shell">npm install --save-dev @types/react @types/react-dom
</code></pre>
<p>至此一个基础的react环境就搭建成功了。</p>
<h2 id="总结">总结</h2>
<p>学习webpack的时候,我也是云里雾里,一开始就对着官方文档看,越看越头大,于是就去找一些关于webpack的文章和视频学习,发现好多都是webpack4的,于是我就对着官方文档的例子改改,找一些webpack5的实现方案等,多动手实践,慢慢的就对webpack的配置有了一点点了解,总的来说,搭建出一个自己的脚手架,还是有点成就感,哈哈。</p><br><br>
来源:https://www.cnblogs.com/handsomezyw/p/16633087.html
頁: [1]
查看完整版本: 从零开始搭建react基础开发环境(基于webpack5)