Webpack 学习笔记

前言

用 Webpack 蛮久的了, 2.0, 3.0, 4.0, 5.0 但由于学的比较杂乱, 所以也没有系统的记入一下. 

这次升级到 5.0 比较有系统的把一些资源记入了起来. 既然走了第一步, 那就顺便写一个简单的学习笔记吧.

内容会涵盖我目前用到的所有东西. Webpack, Sass, Typescript, ESLint, Stylelint, Prettier, Tailwind CSS

另外我的开发环境是 ASP.NET Core, 开发的项目是网站. 所以这些配置不适合用于单页面等等.

 

Why We Need Webpack?

在没有 Webpack 之前, 网站要导入 Javascript 和 CSS 都是直接通过 <script> <link> 导入的. 如果只是几个 file 还好. 

但如果是开发类似电子商务网站的话, 它的 Javascript 和 CSS 代码会非常多, 一旦多管理上就会拆分成多个 file, 彼此依赖.

一旦进入这样情况, 通过 <script> 导入就不太好使了. 而且随着代码多了 CSS, Javascript 就不好维护了. 所以会用到 Sass 和 Typescript.

Webpack 这个时候就派上用场了, 它可以解决 file 依赖的问题, 也可以翻译 Sass 和 Typescript.

 

Webpack Process Flow and Structure

Webpack 的 process flow 大概是 entry -> loader -> plugin -> output

entry 通常是一个 ts, scss file, Webpack 会通过 import 去找它们的依赖,

然后通过 loader 去翻译 (比如, Typescript -> Javascript)

再通过 plugin 做一些而外的加工 (比如, 把做好的 js file 通过 <script> 导入到最终的 html),

最后生成各种 file (比如 .html, .js)

 

跑起来

先来一个最简单的跑起来. 

创建一个 folder > yarn init > create index.js (注意: 如果使用 Yarn 3, 请改成 yarn init -2)

安装 webpack

yarn add webpack --dev
yarn add webpack-cli --dev

package.json 添加 start

"scripts": {
  "start": "webpack --config webpack.config.js --node-env production"
},

--node-env production | development 是为了方便在 1 个 webpack config 下做 conditional 配置 (因为我最终是跑 ASP.NET Core IIS 而不是用 Webpack server), webpack 其实有一个 multiple enviroment config 的概念. 后面也会讲到.

webpack.config.js

const pathHelper = require("path");

module.exports = {
  mode: "production",
  entry: {
    index: "./index.js",
  },
  output: {
    path: pathHelper.resolve(__dirname, "dist"),
  },
};

然后 npm start 就会出现打包好的 dist folder 了. (注意: 如果使用 Yarn 3, 请改成 yarn start)

 

Typescript

刚才用的是 Javascript, 我们来试试换成 Typescript

安装 typescript 和 ts-loader

yarn add typescript --dev
yarn add ts-loader --dev

tsconfig.json (这个可以依据大家的 Typescript 配置, 这里只是我的 example), 可以参考这篇: TypeScript – tsconfig

{
  "compilerOptions": {
    "outDir": "./dist/",
    "module": "es6",
    "target": "es5",
    "allowJs": true,
    "strict": true,
    "sourceMap": true,
    // note: 解忧
    // 目前是 flatpickr 需要到
    // refer : https://stackoverflow.com/questions/53885107/typescript-cant-find-module-flatpickr-even-though-it-includes-typescript-types
    "moduleResolution": "node"
  },
  "exclude": ["wwwroot/assets", "dist"]
}

把刚才的 index.js 换成 index.ts

在 webpack.config.js 添加 loader

所有 loader 的配置大概就是这样, 通过一个正则表达匹配到文件, 然后使用 loader 去翻译.

现在 npm start 就可以看到效果了.

使用 esbuild-loader

esbuild 很火啊, 于是有人做了一个 loader 给 webpack. 它可以提升一点点的速度. 为什么只是一点点?

安装

yarn add esbuild-loader --dev

替换掉 ts-loader 就可以了

// {
//   test: /\.ts$/,
//   use: 'ts-loader',
//   exclude: /node_modules/,
// },
{
  test: /\.ts?$/,
  loader: 'esbuild-loader',
  options: {
    loader: 'ts',
    target: 'es2017',
  },
},

 

ESLint

看这篇 工具 – Prettier、ESLint、Stylelint 做 ESLint 的设置,然后再安装 Webpack ESLint Plugin。

yarn add eslint-webpack-plugin --dev

webpack.config.js 添加

注:ESLint v9.0 以后,ESLintPlugin 需要改写成

new ESLintPlugin({
  overrideConfigFile: path.resolve(__dirname, 'eslint.config.mjs'),
  configType: 'flat',
  extensions: ['js', 'ts'],
  fix: true,
}),

这时 npm start 就会看见报错了.

 

Prettier and Prettier ESLint

Prettier 和 webpack 没有什么关系, 但是和 ESLint 蛮有关系的,所以也就一起写在这一篇了.

Prettier 的主要工作是代码风格和格式化文档. 有了它以后, 保存文档时会同时格式化, 而格式化后又会 auto fix ESLint.

安装 VS Code Prettier 插件

到 VS Code settings.json, 选择 prettier 作为格式化文档工具,

"editor.defaultFormatter": "esbenp.prettier-vscode" (你也可以各别选择文档类型来指定不同的 formatter, 我这里是 default 所有文档)

添加一个 prettier.config.js, VS Code 插件有 2 个方法配置, 1 是通过 settings.json 配置 prettier, 另一个就是通过 prettier.config.js.

module.exports = {
  singleQuote: true,
  arrowParens: 'avoid',
  printWidth: 100,
  overrides: [
    {
      files: '**/*.cshtml',
      options: {
        // note issue:
        // https://github.com/prettier/prettier/issues/10918#issuecomment-851049185
        // https://github.com/heybourn/headwind/issues/127
        // 暂时关掉, 等 issue
        printWidth: Number.MAX_VALUE,
      },
    },
  ],
};

安装 prettier 和 ESLint prettier 插件 (因为我喜欢它的代码风格)

yarn add prettier --dev
yarn add eslint-config-prettier --dev
yarn add eslint-plugin-prettier --dev

在 .eslintrc.json 添加 extends 和 rules

rules

prettier 全场换

参考: Stack Overflow – How do I format all files in a Visual Studio Code project?

有时候 prettier 版本升级后会多一些 rule。这时运行项目就会有很多报错。我们不可能一个一个文件打开 save auto fix。

所以需要 run command 来解决。

首先全局安装 npm install prettier --global

然后在项目力 prettier --write ./**/*.ts 

 

Sass

先说说 CSS 怎样打包. 

安装 Loader, mini-css-extract-plugin 是在 production 情况下取代 style-loader 用的

yarn add style-loader --dev
yarn add css-loader --dev
yarn add mini-css-extract-plugin --dev

webpack.config.js

const isProduction = process.env.NODE_ENV === 'production';
const MiniCssExtractPlugin = require("mini-css-extract-plugin");


plugins: [
  ...(isProduction ? [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css',
      chunkFilename: '[id],[contenthash].css'
    })
  ] : [])
],
modules: {
  rules: [
    {
      test: /\.css$/,
      use: [isProduction ? MiniCssExtractPlugin.loader : 'style-loader', 'css-loader']
    }
  ]
}
View Code

直接在 .js 里头 import .css 文档

在 development 阶段, CSS 代码会被写入 .js 里, 渲染的速度会慢半拍, 在 production 阶段会生产 .css 文档. 渲染是正常的.

接着是 Sass 打包

把 .css file 换成 .scss, 安装

yarn add sass --dev
yarn add sass-loader --dev

添加 loader

正则换掉 test: /\.scss$/

这样就可以了, 另外还需要一个 url-resolve-loader, 因为 webpack 和 Sass loader 对引入 asset 路径不太聪明, 具体什么问题看这里.

安装

yarn add resolve-url-loader --dev

添加 loader

rules: [
  {
    test: /\.scss$/,
    use: [
      isProduction ? MiniCssExtractPlugin.loader : 'style-loader', 
      'css-loader', 
      {
        loader: 'resolve-url-loader',
        options: {
          sourceMap: true
        }
      },
      {
        loader: 'sass-loader',
        options: {
          sourceMap: true
        }
      }
    ]
  }
]
View Code

 

Tailwind CSS and PostCSS

我没有独立使用 PostCSS, 我是因为 Tailwind CSS 才接触到 PostCSS 的. 所以这里就一起上呗.

这里随便讲一下 Tailwind 的机制. 它需要一个 config file 也只能有一个 (这对我是个问题),

Tailwind 有一个很大的 utilities code, 但它可以 purge 来优化 (有点向 js 的 tree shaking) 

通过 config file 会声明要扫描的 html, js 文档, 文档里面有使用到的 Tailwind class 最终才会被打包出来.

它的过程大概是, 扫描以后, 把没有用到的 utilities code 清楚掉, 然后当 scss file 内有 @import 'tailwindcss/utilities', 它就会把所有用到的 ultilities code 丢进去. 

注意: 不要到处 import utilities 哦, 不然你的 css file 会非常大, 合理的做法是做一个 shared.scss 由它负责 import, 然后其余的 file 再使用这个 shared. 这样 webpack 就可以通过 common 的概念共享这些代码了.

安装

yarn add postcss --dev
yarn add postcss-import --dev
yarn add postcss-loader --dev
yarn add postcss-preset-env --dev
yarn add tailwindcss --dev
yarn add glob --dev

最后一个 glob 是为了解决 Tailwind 只能用一个 config, 而我需要多个 config (去控制扫描的文档), 才需要安装的.

postcss-preset-env 是比 autoprefixer 更好的东西, 它里面包含了 autoprefixer 了, 所以就不需要装 autoprefixer 了 (不安装的话, yarn 会有 warning 哦, 因为 Tailwind has unmet peer dependency, 不过不要紧 preset-env 有包含了)

webpack.config.js

const glob = require('glob');

直接把 Tailwind 的 config 搬进来 webpack.config.js 里, 通过 loader 来实现 multiple 区域 purge. 我面对的问题是, PDF template 我也想使用 Tailwind, 但是不可能把 PDF 需要的 style 也和网站的 style 一起打包.

不然就无端端增加了网站的 style 代码量丫.

顺便介绍一个 prettier-plugin-tailwindcss, 它的功能和 headwind 类似.

yarn add prettier-plugin-tailwindcss --dev

这里有一些我遇到 Tailwind 的问题也记入在这里吧.

1. Tailwind CSS class, prettier, headwind 打架, issue1, issue2

2. prettier-plugin-tailwind formating 非常慢 issue, 不支持 Tailwind CSS 3.0, no longer maintain, Tailwind CSS 有 build-in 的咯 

3. Tailwind CSS intellisense 一定要 tailwindcss.config.js. issue (这个是我把 config 放入 webpack.config.js 之后引起的)

4. Tailwindcss jit infinity compile. issue 这个也是因为我要 multiple config 才遇到的. workaround 是使用 glob.sync 把抽象路径变成具体 (也就是我上面给的例子)

 

注: glob 9.0 有一些 breaking change, 要改成 glob.globSync(value).map(v => v.replace(/\\/g, '/')).

postcss-font-magician

这个是我用的一个插件, 顺便介绍一下, 它的功能是帮写 font 的代码. 我们只要再 config 设置好所有我们需要用到的 font 然后只要我们 scss 里头有写到. 那么它就会把我们补上 css import 代码.

它有一些 build-in 的 download 路径 (直接连 cdn 那种).

它内部其实依赖了 google-fonts-complete

google-fonts-complete 有 json 记入所以 Google Fonts 的 src url 

里面长这样

但是这个包已经没有 update 2,3 年了, 如果要用到比较新的 font, 最好是自己 config 放入链接.

安装

yarn add postcss-font-magician --dev

添加 config

它默认支持 format 是 local, woff2, woff, eot 没有 TTF, 如果有使用 TTF 就要加进去.

 

Stylelint

安装

yarn add stylelint --dev
yarn add stylelint-config-prettier --dev
yarn add stylelint-config-standard-scss --dev
yarn add stylelint-prettier --dev
yarn add stylelint-scss --dev

VS Code 插件

stylelint.config.js

module.exports = {
  extends: ['stylelint-config-standard-scss', 'stylelint-prettier/recommended'],
  ignoreFiles: ['dist/*', 'wwwroot/assets/*'],
  plugins: ['stylelint-scss'],
  rules: {
    'at-rule-no-unknown': null,
    'scss/at-rule-no-unknown': [
      true,
      {
        // note 解忧: 之前需要,现在好像不需要了
        // ignoreAtRules: ['tailwind', 'apply', 'variants', 'responsive', 'screen'],
      },
    ],
    'declaration-block-trailing-semicolon': null,
    'no-descending-specificity': null,
    'prettier/prettier': [
      true,
      {
        singleQuote: true,
        endOfLine: 'auto', // refer: https://stackoverflow.com/questions/53516594/why-do-i-keep-getting-delete-cr-prettier-prettier
      },
    ],
  },
};

Webpack Plugin

yarn add stylelint-webpack-plugin

const StylelintWebpackPlugin = require("stylelint-webpack-plugin");

new StylelintWebpackPlugin({
  configFile: pathHelper.resolve(__dirname, 'stylelint.config.js'),
  files: '**/*.scss',
  fix: true,
})
View Code

 

clean-webpack-plugin

它的功能是每次打包的时候会自动 clear 掉 dist folder 里面的所有文档, 如果没有它的话, 文档是被覆盖而已.

安装

yarn add clean-webpack-plugin --dev

webpack.config.js

 

html-webpack-plugin

上边我们都是打包 ts, scss, 最终还需要导入到 index.html 里头.

html-webpack-plugin 就是负责这个 part 的. 

安装

yarn add html-webpack-plugin --dev

webpack.config.js

 一个 new HtmlWebpackPlugin 代表一个 page. 要多个 page, new 多个就行了.

Output 的 js production 情况要加上 hash 哦.

上面这个 config 就表示, 拿 index.html 放入 chunks index (还有它所有连接到的 scss, ts 等等) 最后输出 index.html

效果

自定义 append script 的地方

有时候我们会需要自己控制 append 的位置, 比如 ASP.NET Core 一般是要放去 layout 的

配置 inject = false

在 chtml 页面 

@section Css{
<% for (key in htmlWebpackPlugin.files.css) { %>
  <link href="<%= htmlWebpackPlugin.files.css[key] %>" rel="stylesheet">
  <% } %>
    }

@section Script{
    <% for (key in htmlWebpackPlugin.files.js) { %>
      <script defer src="<%= htmlWebpackPlugin.files.js[key] %>"></script>
      <% } %>
        }

如果是 AMP page 的话还需要直接把 code 打在 style 里

@section Css{
<style amp-custom>
  <%=htmlWebpackPlugin.files.css.map(cssFile=> compilation.assets[cssFile.substr(htmlWebpackPlugin.files.publicPath.length)].source().replace(new RegExp('@@'.substring(1), 'g'), '@@')).join('') %>
</style>
}

 

Webpack resolve extensions

参考

这个的功能是让我们在 import 的时候可以省略掉写 extensions

本来是 import 'index.ts' 变成 import 'index' 就可以了. 我一般上只是放 ts,js 而已, 如果遇到同名字,它会匹配最前的一个

 

Asset

Webpack 4.x 的时候需要一些 plugin 来搞 asset (image 等). 5.0 以后有 build-in 的了.

在 output 加多一个 assetModuleFilename

output: {
  path: pathHelper.resolve(__dirname, './dist'),
  filename: isProduction ? '[name].[contenthash].js' : '[name].js',
  assetModuleFilename: 'assets/[name]-[hash]-[ext][query]', // 加入这个
},

在 module 加入 2 个 loader

module: {
  rules: [
    // ...
    {
      test: /\.(svg|gif|jpe?g|png|webp)$/i,
      type: 'asset/resource',
    },
    {
      test: /\.(woff|woff2|eot|ttf|otf)$/i,
      type: 'asset/resource',
    },
  ],
},

 html 引入 image

<img src="<%= require('./images/yangmi.jpg') %>" />

效果

<img src="assets/yangmi-12a261793f70b6685335-.jpg" />

注意: require('./icons/icons/svg-sprite.svg#facebook') 会自动把 #facebook 清掉哦, 解决方案是把它放外面

<svg class="icon">
  <!-- <use xlink:href="../icons/svg-sprite/sprite.svg#facebook" /> -->
  <use xlink:href="<%= require('../icons/svg-sprite/sprite.svg') %>#facebook" />
</svg>

 

html-loader

不喜欢 require 的方式也可以用 html-loader 替代

yarn add html-loader --dev

加入 loader

就可以像平常一样 import 了

当 html-loader 撞 html-webpack-plugin

如果有用到 html-webpack-plugin 的自定义 append (ejs 语法). html-loader 会破坏掉它.

参考:

stackoverflow – How to get both EJS compilation and html-loader in html-webpack-plugin?

stackoverflow – Prevent HtmlWebpackPlugin from minimizing line breaks in production

Github Issus – Overwrite HtmlWebpackPlugin <%= %> expression

所以呢, 有使用 html-webpack-plugin 自定义, 最好就放弃用 html-loader, 改成 require 调用, 或者用 ejs loader.

 

Gulp for inline style email template

通常 email template 是需要 inline style 的, 而且不建议使用 Tailwind CSS, 因为 email client 兼容性很差的. 新的 CSS 都不太支持, 而 Tailwind CSS 一般上会用到 modern 的 CSS style

做法是这样, 通过 webpack 打包好 style 然后使用 gulp 变成 inline (之前 webpack 是有 plugin 的, 但后来没有维护了)

安装

yarn add gulp --dev
yarn add gulp-inline-css --dev

gulpfile.js

const gulp = require('gulp');
const inlineCss = require('gulp-inline-css');

gulp.task('inlineCss', () => {
  return gulp
    .src('./EmailTemplate/**/Index.cshtml')
    .pipe(
      inlineCss({
        removeHtmlSelectors: true,
      })
    )
    .pipe(gulp.dest('./EmailTemplate'));
});

package.json

"scripts": {
  "start": "webpack --config webpack.config.js --node-env production & gulp inlineCss"
},

use in Razor

Razar 会有 C# 的语法, 比如泛型. 它会用到 <>. 而 inline css 会把它 convert to lowercase.

比如上面的 <BusinessOptions> compile 后会变成 <businessoptions> 因为它被当成 element 处理了.

解决方法是让 inline css by pass Razor 语法. 参考: npm – gulp-inline-css

gulp.task('inlineCss', () => {
  return gulp
    .src('./EmailTemplate/**/Index.cshtml')
    .pipe(
      inlineCss({
        removeHtmlSelectors: true,
        codeBlocks: {
          EJS: { start: '<%', end: '%>' },
          HBS: { start: '{{', end: '}}' },
          Razor: { start: '@{', end: '}' },
        },
      })
    )
    .pipe(gulp.dest('./EmailTemplate'));
});

添加上 codeBlocks options.

然后把 @inject 放入到 @{} 里头就可以了

 

Optimization

Optimization 是做 common.js 的. 因为我们有很多 js file 会互相 import. 虽然说每一个页面最好只加载当前页面所需要的 js 代码.

但是有些 js 代码可能每一页都需要, 如果把这些 js 代码分别打包进去每一页的 script, 那么每一页的 script 就大了, 每一页的加载就慢.

所以就有了 optimization 去做这些 trade off.

它可以依据, 多少地方重复使用, 多大的 file, 去决定是否要被抽出来当作 common.js.

还有一个叫 vendor.js, 就是负责把 js library (比如 jQuery) 分出来服用的.

我这里就不细谈了, 参考链接吧

SplitChunksPlugin

Minimize terser

SplitChunksPlugin 详解

Module, Chunk, Bundle 的区别

安装

yarn add css-minimizer-webpack-plugin --dev
yarn add terser-webpack-plugin --dev

这是我 ASP.NET Core 项目的配置. webpack.config.js

const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
optimization: {
  moduleIds: 'deterministic',
  runtimeChunk: 'single',
  minimize: true,
  minimizer: [
    new TerserPlugin({
      terserOptions: {
        format: {
          comments: false,
        },
      },
      extractComments: false,
    }),
    new CssMinimizerPlugin({
      minimizerOptions: {
        preset: ['default', { cssDeclarationSorter: false }],
      },
    }),
  ],
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      ...(() => {
        const extraWebPages = [
          { camelCase: 'account', kebabCase: 'account', pascalCase: 'Account' },
          {
            camelCase: 'landingPage',
            kebabCase: 'landing-page',
            pascalCase: 'LandingPage',
          },
        ];
        const ignorePages = [
          {
            camelCase: 'emailTemplate',
            kebabCase: 'email-template',
            pascalCase: 'EmailTemplate',
          },
          {
            camelCase: 'pdfTemplate',
            kebabCase: 'pdf-template',
            pascalCase: 'PdfTemplate',
          },
        ];

        let cacheGroups = {
          default: false,
          defaultVendors: false,
          ...generateCacheGroup({
            name: { camelCase: 'web', kebabCase: 'web' },
            excludeds: [
              ...extraWebPages.map(extraWebPage => `-${extraWebPage.kebabCase}-`),
              ...ignorePages.map(ignorePage => `^${ignorePage.kebabCase}-`),
            ],
          }),
        };
        for (const extraWebPage of extraWebPages) {
          const firstCharToUpperCase = value =>
            value.substring(0, 1).toUpperCase() + value.substring(1);

          cacheGroups = {
            ...cacheGroups,
            ...generateCacheGroup({
              name: {
                camelCase: `web${firstCharToUpperCase(extraWebPage.camelCase)}`,
                kebabCase: `web-${extraWebPage.kebabCase}`,
              },
              only: `-${extraWebPage.kebabCase}-`,
              excludeds: ignorePages.map(ignorePage => `^${ignorePage.kebabCase}-`),
            }),
          };
        }
        return cacheGroups;
        function generateCacheGroup(config) {
          const { name, excludeds, only } = config;
          const cacheGroup = {};
          const chunkFn = chunk => {
            if (excludeds.some(excluded => new RegExp(excluded).test(chunk.name))) {
              return false;
            }

            if (only !== undefined && !new RegExp(only).test(chunk.name)) {
              return false;
            }

            return true;
          };

          cacheGroup[`${name.camelCase}Vendors`] = {
            name: `${name.kebabCase}-vendors`,
            chunks: chunkFn,
            test: /[\\/]node_modules[\\/]/,
            priority: -10,
          };
          cacheGroup[`${name.camelCase}Commons`] = {
            name: `${name.kebabCase}-commons`,
            chunks: chunkFn,
            minChunks: 2,
            minSize: 50 * 1024,
            priority: -20,
          };
          return cacheGroup;
        }
      })(),
    },
  },
}

account 是 login 界面, 是 control panel, 所以我刻意把它和一般的网页分开了.

minSize 的 unit 是 byte

Webpack 有 default 的 split chunk config, 通过 

default: false,
defaultVendors: false,

把它们关掉, 其它的可以 override

Webpack server 单侧的配置 webpack.config.js

  ...(isProduction
    ? {
        optimization: {
          moduleIds: 'deterministic',
          runtimeChunk: 'single',
          minimize: true,
          minimizer: [
            new TerserPlugin({
              terserOptions: {
                format: {
                  comments: false,
                },
              },
              extractComments: false,
            }),
            ...(isProduction ? [new CssMinimizerPlugin()] : []),
          ],
          splitChunks: {
            chunks: 'all',
            cacheGroups: {
              commons: {
                name: 'commons',
                chunks: 'all',
                minChunks: 2,
                minSize: 1,
              },
              vendors: {
                name: 'vendors',
                test: /[\\/]node_modules[\\/]/,
                chunks: 'all',
              },
            },
          },
        },
      }
    : undefined),
View Code

更新: 16-02-2023 踩坑了 /.\

css-minimizer-webpack-plugin 底层用的是 cssnano 做 minify. 它默认设置是会排序 style 属性的. 这可能破坏掉原有的表达, 所以最好是关掉它

new CssMinimizerPlugin({
  minimizerOptions: {
    preset: [
      'default',
      {
        cssDeclarationSorter: false,
      },
    ],
  },
}),

相关参考: 参考1, 参考2, 参考3

 

Webpack Server

如果是用 ASP.NET Core IIS 的话,就不可能用 Webpack server 了, 但有时候只是要做一些小测试的话, 还是不错用的. 毕竟 Build 不是很快呀.

安装

yarn add webpack-dev-server --dev
yarn add webpack-merge --dev

做 3 个 webpack config, 1 个是抽象, 1 个是 dev (跑 webpack server, 它会把 build 好的 file 放在 ram 里), 1 个是 production (build file 出来)

 webpack.dev.js, 我这里还附上了 IP 和 SSL 版本, 这样手机也方便跑

/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require('fs');
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const webpack = require('webpack');

module.exports = merge(common, {
  devtool: 'inline-source-map',
  devServer: {
    open: 'about-page.html',
    host: '192.168.1.152',
    port: 44301,
    https: {
      key: fs.readFileSync('C:\\self-signed-certificate\\192.168.1.152.key'),
      cert: fs.readFileSync('C:\\self-signed-certificate\\192.168.1.152.crt'),
    },
    hot: true,
  },
  plugins: [
    new webpack.DefinePlugin({
      // 'process.env.NODE_ENV': 'development'
    }),
  ],
});

webpack.prod.js

/* eslint-disable @typescript-eslint/no-var-requires */
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const webpack = require('webpack');

module.exports = merge(common, {
  // devtool: 'source-map',
  plugins: [
    new webpack.DefinePlugin({
      // 'process.env.NODE_ENV': JSON.stringify('production')
    }),
  ],
});

 package.json

"scripts": {
  "start": "webpack serve --config webpack.dev.js --node-env development",
  "build": "webpack --config webpack.prod.js --node-env production & gulp inlineCss"
},

index.html no auto refresh when hot relead open

Page does not refresh on HTML change when HMR is on

hot relead 开启后, ts, scss change 是不刷新游览器的. 视乎也导致了 index change 也不刷新. 

我没有打算解决这个问题, 只是 research 了一下. 上面提到的方案已经过时了. 

migration 可以参考这个, 以后不耐烦才来 fix 呗.

 

关于 hash 的小知识

hash 的目的是做永久 caching. 通常我们使用 contenthash, 当文件 (js, css, image) 内容修改后, hash 就不一样了.

Webpack 默认使用 md4 进行 hash. 为什么用这么古老的呢? 不清楚, 可能是比较快吧, 毕竟这里不需要考虑安全问题.

那我们想换成 sha256 也是可以的.

声明一个 hashFuncton : 'sha256' 即可

这个 sha256 用 C# 实现的话是这样的

public static class Program
{
    public static async Task Main()
    {
        var rootPath = Path.Combine(AppContext.BaseDirectory, @"..\..\..\");
        var fileRootFullPath = Path.Combine(rootPath, "panasonic-8aa06e7a117e778818e8.webp"); // 这个是 webpack generate 出来的 hash
        var fileBytes = await File.ReadAllBytesAsync(fileRootFullPath);
        using var sha256 = SHA256.Create();
        var hashBytes = sha256.ComputeHash(fileBytes);
        var hashString = BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
        var final = hashString[0..20];
        Console.WriteLine(final); // 8aa06e7a117e778818e8 这个是 C# generate 的 hash. 两者是完全一致的.
    }
}

 

参考: 

如何从头开始设置 webpack 5

中文官网指南

概念

DevServer 安装

DevServer HTTPS

Asset

缓存

缓存

Long Term Cache

Optimization

Minimize terser

SplitChunksPlugin 详解

Module, Chunk, Bundle 的区别

Plugin:

HtmlWebpackPlugin

MiniCssExtractPlugin

Loader:

Typescript

ESLint

Asset

CSS

Sass

PostCSS

Tailwind CSS

Tailwind CSS PostCSS

Prettier, ESLint, Stylelint

Integrating with Linters

ESLint Plugin Prettier

Stylelint

How to use Prettier with ESLint and TypeScript in VSCode

typescript-eslint

typescript-eslint Plugin

Email Inline Style

Juice (low layer API)

HTMLWebpackInlineStylePlugin (no maintain any more, only support < v4)

Can I Use Style in Email?

Can I Use Style in Email? (2)

gulp-inline-css

AMP Style Source

html-webpack-plugin inject false

.source() sample

html-webpack-plugin find "inline template example"

 

posted @ 2021-10-08 18:40  兴杰  阅读(289)  评论(0编辑  收藏  举报