webpack5 + webpack-chain 构建一个大型应用系列 2(附 vscode 跟 prettier 配置)

继上一篇 一步步从零开始用 webpack 搭建一个大型项目 以后的第二篇。本文使用了 webpack5 将项目进行了重构,并全程使用的 webpack-chain 来配置 webpack,每一个功能也都是独立文件,可单独使用。所以该项目的配置能够在任何项目中被使用。此项目可实战亦可当成 webpack 手册来学习。我开发这个项目的目的就是不管你是新手仍是有经验的大佬均可以从中有所收获。此项目为想学 webpack 的同窗提供了很好的实战平台,每个 插件 每个 loader 都会有详细的讲解及使用背景。javascript

为了节省你们时间,提高学习效率,我想要将全部 webpack 相关的系列都集成在这里,这里的每个优化都是通过我反复推敲实践而来,也会吸收一些优秀的开源库来完善它,此项目将长期维护,也诚挚欢迎全部人参与到该项目当中,一块儿成为该项目的共建者!css

项目地址:github.com/luoxue-vict…html

由于本文是使用 webpack5 进行了重构,那我就先将第 14 课时放在前面讲了前端

本文目录vue

接下来计划去作的 TODO github.com/luoxue-vict…java

课时 14:升级 webpack5

本章主要将项目升级到 webpack5,先踩一下坑。把踩坑的通过给你们分享一下。node

webpack5 更像是一个黑盒了,好多以前必需要用插件来完成的工做,如今 webpack5 内部已经被集成了,开箱即用。webpack5 主要为了优化编译速度、更多的默认配置(内置了更多的配置)、更好的代码生成、为未来 webpack 走的更远作铺垫。react

本章概要webpack

webpack5 作了哪些事情?

  • 使用长期缓存提高编译速度
  • 使用更好的算法和默认值来改善长期缓存
  • 经过更好的 Tree Shaking 和 Code Generation 来改善 bundle 大小
  • 重构内部结构,在不引入任何重大更改的状况下实现 v4 的功能
  • 经过引入重大更改来为未来的功能作准备,以使咱们可以尽量长时间地使用 v5

升级 webpack5

本教程能够经过脚手架命令一键升级/降级git

webpack-box upgrade 5/4
复制代码

主要升级了两个插件,其余使用到的模块都已经被兼容,html-webpack-plugin 插件由于涉及到热更新,目前热更新的 bug 尚未修复,因此你们切换到 webpack5 以后的第一次编译能够成功,可是保存后的再次编译会报错(这点我会持续关注,一旦修理当即更新版本)

package.json

{
  "html-webpack-plugin": "^4.0.0-beta.11",
  "webpack": "^5.0.0-beta.9"
}
复制代码

编译速度对比

webpack-box build index
复制代码

Version: webpack 4.41.2

如下是使用了 cache-loader

  • 第一次 4216ms
  • 第二次 2781ms
  • 第三次 2827ms
  • 第四次 2797ms

Version: webpack 5.0.0-beta.9

使用了 持久缓存

  • 第一次 3567ms
  • 第二次 2602ms
  • 第三次 2609ms
  • 第四次 2582ms

能够看出来 webpack5 使用持久缓存的状况下比 webpack4 使用 cache-loader 的编译速度快了 100ms ~ 200ms,因此之后就没有必要使用 cache-loader,webpack5 提供了更好的算法跟更优秀的缓存方案

webpack4 到 webpack5 的变化

1. cache-loader 再也不须要

使用持久性缓存时,您再也不须要缓存加载器。与 babel cacheDirectory 相同。

2. html-webpack-plugin 问题

一些错误并修复 error

  1. Cannot add property htmlWebpackPluginAlterChunks, object is not extensible

安装 4.x 版本可修复

npm i html-webpack-plugin@4.0.0-beta.11
复制代码
  1. Cannot read property 'get' of undefined

未修复 第一次编译生效,保存以后会报错,webpack5 对热更新进行了重写,致使 html-webpack-plugin 不兼容,缘由可查

3. 动态加载的文件终于有名字了,再也不是 id,而是改成项目路径的拼接

可使用 optimization.chunkIds 进行修改

点击看文档

module.exports = {
  //...
  optimization: {
    chunkIds: "named"
  }
};

// 链式修改
config.optimization.set("chunkIds", "natural");
复制代码

4. 嵌套 tree-shaking

以下,在 webpack4 中 a、b 都会被打包进 bundle 中,webpack5 会对嵌套的无用代码也会删除掉,也就是说 b 并不会被打包进 bundle 中了,由于 b 并无被使用到

// inner.js
export const a = 1;
export const b = 2;

// module.js
import * as inner from "./inner";
export { inner };

// user.js
import * as module from "./module";
console.log(module.inner.a);
复制代码

5. 内部模块 tree-shaking

webpack5 会检查都模块内部的方法是否被使用,若是没有被使用的话,那么会把模块内部调用的方法也会被删除

可是前提是你要知道这些代码是无反作用的,否则颇有可能将你的代码删掉,好比你要写一个组件,而你库里并无使用它,那么就有可能在打包的时候被 tree-shaking 了

使用它您须要在 package.json 中配置 "sideEffects": false,而且设置 optimization.usedExports 为 true

// package.json
{
  "sideEffects": false
}

// config/optimization.js
config.optimization.usedExports(true);
复制代码

代码演示

import { something } from "./something";

function usingSomething() {
  return something;
}

export function test() {
  return usingSomething();
}
复制代码

若是外部模块没有使用 test 方法的话,那么 usingSomething、something 也会在 bundle 中被删除

6. 改进代码生成

告诉 webpack webpack 生成代码的最大 EcmaScript 版本

webpack4 仅能支持到 ES5,webpack5 支持 ES5 跟 ES6

ecmaVersion 的取值范围 5 ~ 11 或 2009 ~ 2020,webpack5 默认采用最小值 5

config.output.set("ecmaVersion", 6);
复制代码

7. SplitChunks and Module Sizes

webpack5 能够根据不一样类型的文件分别设置 splitChunks 打包的尺寸,默认状况下只针对 javascript 进行分离打包

config.optimization.splitChunks({
  minSize: {
    javascript: 30000,
    style: 50000
  }
});
复制代码

8. 持久缓存

webpack5 提供了两种缓存方式,一种是持久缓存将文件缓存在文件系统,另外一种是缓存在内存里

// type {filesystem | memory}
config.cache({
  type: "filesystem"
});
复制代码

默认状况下,会被缓存在 node_modules/.cache/webpack 下,您能够经过 cacheDirectory 选项修改缓存目录

9. 其余

webpack5 具体调整内容点这里

课题 10:添加 eslint 并开启自动修复

本章概要

抽离 cwd 层,职能单一化

目的:让职能更加清晰,每一层只作一件事情,使用标准化的 api 去处理同类逻辑

  • PluginAPI 处理脚手架插件逻辑
  • CommandAPI 处理脚手架命令行逻辑
  • cwd 抽离 command 层
└── api
   │── CommandAPI.js
   └── PluginAPI.js
└── cwd
   │── build:ssr.js
   │── build.js
   │── dev.js
   │── dll.js
   │── lint.js
   └── ssr:server.js
复制代码

配置 eslint-loader

配置 eslint-loader,在 webpack-box dev 时会检测 eslint 规则,若是有报错会显示在控制台上

config.module
  .rule("eslint")
  .pre()
  .exclude.add(/node_modules/)
  .end()
  .test(/\.(vue|(j)sx?)$/)
  .use("eslint-loader")
  .loader(require.resolve("eslint-loader"))
  .options({
    extensions,
    cache: true,
    cacheIdentifier,
    emitWarning: allWarnings,
    emitError: allErrors,
    eslintPath: path.dirname(
      resolveModule("eslint/package.json", cwd) ||
        resolveModule("eslint/package.json", __dirname)
    ),
    formatter: loadModule("eslint/lib/formatters/codeframe", cwd, true)
  });
复制代码

eslint 自动修复功能

当咱们项目改变某一个规则时,咱们项目中都会出现大量的错误,咱们确定不但愿手动一个个去修改,因此咱们须要使用 eslint 的自动修复的功能,它可以帮助咱们修复绝大数的错误,还有一些修复不了的咱们再手动修复就能够了

这里写出了部分代码,更多细节能够在项目里面看

packages/eslint/lint.js

const { CLIEngine } = loadModule("eslint", cwd, true) || require("eslint");
const config = Object.assign({
  extensions,
  fix: true,
  cwd
});
const engine = new CLIEngine(config);
const defaultFilesToLint = ["src", "tests", "*.js", ".*.js"].filter(pattern =>
  globby
    .sync(pattern, { cwd, absolute: true })
    .some(p => !engine.isPathIgnored(p))
);

const files = args._ && args._.length ? args._ : defaultFilesToLint;
const report = engine.executeOnFiles(files);
if (config.fix) {
  CLIEngine.outputFixes(report);
}
复制代码

添加脚手架命令

咱们但愿经过命令行的形式去修复,webpack-box lint eslint,因此须要在 cwd 层添加命令行

cwd/lint.js

module.exports = function(injectCommand, api) {
  injectCommand(function({ program, cleanArgs, boxConfig }) {
    program
      .command("lint [type]")
      .description("修复lint")
      .action(async (name, cmd) => {
        const options = cleanArgs(cmd);
        const args = Object.assign(options, { name }, boxConfig);
        require("../build/lint")(args, api);
      });
  });
};
复制代码

这样咱们可使用 webpack-box lint eslint 去修复大部分的错误了,去试一下吧~

使用编译器自动修复

固然咱们执行 webpack-box lint eslint 命令时能够去修复一些错误,可是当咱们写代码时但愿编译器可以帮助咱们自动修改,而不是等到代码写完了才去校验,这样会给咱们带来二次麻烦,甚至会出现修复不了的问题。

因此咱们使用 vscodeeslint 插件来帮助咱们实现吧

首先您必须使用的编译器是 vscode,固然其它的编译器也能够,可是咱们这里只讲 vscode 的配置。

您安装了 eslint 插件后,须要在设置中设置 "eslint.autoFixOnSave": true,这样就能够在保存时自动修复 eslint 的错误了

固然您可能只在这个项目中使用了 eslint,而在其余项目中并不须要保存时修复

能够在根目录添加

└── .vscode
   └── settings.json
复制代码

放一份我本身的配置供你们参考

{
  /*
   * @description 编译器配置
   * @param tabSize 默认tab为两个空格
   * @param formatOnSave 保存时自动修复
   */
  "editor.tabSize": 2,
  "editor.formatOnSave": true,
  /*
   * @description eslint 配置
   * @param alwaysShowStatus 配置
   * @param autoFixOnSave 保存时自动修复
   * @param validate 在vue中添加错误提示
   */
  "eslint.alwaysShowStatus": true,
  "eslint.autoFixOnSave": true,
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    {
      "language": "vue",
      "autoFix": true
    }
  ],
  /*
   * @description tslint 配置
   * @param autoFixOnSave 保存时自动修复
   * @param alwaysShowRuleFailuresAsWarnings 全部特征都是用 Warnings
   */
  "tslint.autoFixOnSave": true,
  "tslint.alwaysShowRuleFailuresAsWarnings": true,
  /*
   * @description stylelint 配置
   * @param autoFixOnSave 保存时自动修复
   */
  "stylelint.autoFixOnSave": true,
  /*
   * @description vetur 配置
   */
  "vetur.format.defaultFormatter.html": "prettier",
  "vetur.format.defaultFormatterOptions": {
    "prettier": {
      "semi": false,
      "singleQuote": true
    }
  },
  /*
   * @description 配置编辑器设置以覆盖某种语言
   */
  "[typescript]": {
    // "editor.defaultFormatter": "esbenp.prettier-vscode"
    "editor.defaultFormatter": "eg2.tslint"
  },
  "[html]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[jsonc]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[vue]": {
    "editor.defaultFormatter": "eg2.tslint"
  },
  "[javascript]": {
    "editor.defaultFormatter": "dbaeumer.vscode-eslint"
  },
  "[javascriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }
}
复制代码

代码提交检查(lint-staged)

上述的操做都是咱们理想状态下的检测跟修复,可是有时还会遇到意外的状况,并无 lint 代码就提交了,这样会致使可能出现问题,因此咱们须要在提交代码前进行一次代码检验

在 package.json 中添加 lint-staged,在代码提交时会先执行 lint,lint 经过以后才能提交成功

package.json

{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.{js,jsx}": ["webpack-box lint eslint", "git add"]
  }
}
复制代码

课题 11:添加 stylelint 并开启自动修复

本章概要

配置 stylelint standard 插件

使用 stylelint-config-standard 插件

.stylelintrc.js

module.exports = {
  root: true,
  extends: "stylelint-config-standard"
};
复制代码

配置 stylelint 插件

module.exports = ({
  config,
  options: { stylelint: { lintOnSave = false, extensions } = {} },
  api
}) => {
  const StyleLintPlugin = require("stylelint-webpack-plugin");
  const CodeframeFormatter = require("stylelint-codeframe-formatter");
  const stylelint = [];
  return () => {
    config.plugin("stylelint").use(StyleLintPlugin, [
      Object.assign(
        {
          failOnError: lintOnSave === "error",
          files: ["src/**/*.{vue,htm,html,css,sss,less,scss}"],
          formatter: CodeframeFormatter
        },
        stylelint
      )
    ]);
  };
};
复制代码

自动修复

module.exports = async function lint({ api, args = {}, pluginOptions = {} }) {
  const cwd = api.resolve(".");

  const files =
    args._ && args._.length
      ? args._
      : [cwd + "/src/**/*.{vue,htm,html,css,sss,less,scss}"];
  if (args["no-fix"]) {
    args.fix = false;
    delete args["no-fix"];
  }

  const { formatter } = args;
  if (
    formatter &&
    typeof formatter === "string" &&
    !["json", "string", "verbose"].includes(formatter)
  ) {
    try {
      args.formatter = require(formatter);
    } catch (e) {
      delete args.formatter;
      if (typeof pluginOptions.formatter !== "function") {
        console.log(
          format(
            chalk`{bgYellow.black WARN }`,
            chalk`${e.toString()}\n{yellow Invalid formatter}`
          )
        );
      }
    }
  }

  const options = Object.assign(
    {},
    {
      configBasedir: cwd,
      fix: true,
      files,
      formatter: CodeframeFormatter
    },
    pluginOptions,
    normalizeConfig(args)
  );

  try {
    const { errored, results, output: formattedOutput } = await stylelint.lint(
      options
    );
    if (!errored) {
      if (!args.silent) {
        const hasWarnings = results.some(result => {
          if (result.ignored) {
            return null;
          }
          return result.warnings.some(
            warning => warning.severity === "warning"
          );
        });
        if (hasWarnings) {
          console.log(formattedOutput);
        } else {
          console.log(
            format(
              chalk`{bgGreen.black DONE }`,
              `stylelint 没有发现错误!${ options.fix ? chalk` {blue (已经自动修复)}` : "" }`
            )
          );
        }
      }
    } else {
      console.log(formattedOutput);
      process.exit(1);
    }
  } catch (err) {
    console.log(
      format(chalk`{bgRed.black ERROR }`, err.stack.slice(" Error:".length))
    );
    process.exit(1);
  }
};
复制代码

代码提交检查

{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.{vue,htm,html,css,sss,less,scss}": [
      "webpack-box lint stylelint",
      "git add"
    ],
    "*.{js,jsx}": ["webpack-box lint eslint", "git add"]
  }
}
复制代码

课题 12:添加 tslint 并开启自动修复

本章概要

配置插件

config/tslintPlugin.js

module.exports = ({
  config,
  options: { tslint: { lintOnSave = false, useThreads = false } = {} },
  api
}) => {
  const fs = require("fs");
  return () => {
    config.plugin("fork-ts-checker").tap(([options]) => {
      options.tslint =
        lintOnSave !== false && fs.existsSync(api.resolve("tslint.json"));
      options.formatter = "codeframe";
      options.checkSyntacticErrors = useThreads;
      return [options];
    });
  };
};
复制代码

添加规则

tslint.json

{
  "defaultSeverity": "warning",
  "extends": ["tslint:recommended"],
  "linterOptions": {
    "exclude": ["node_modules/**"]
  },
  "rules": {
    "max-classes-per-file": [true, 5, "exclude-class-expressions"],
    "quotemark": [true, "single"],
    "semicolon": [true, "never"],
    "indent": [true, "spaces", 2],
    "ordered-imports": false,
    "object-literal-sort-keys": false,
    "no-consecutive-blank-lines": false,
    "disable-next-line": false,
    "only-arrow-functions": false,
    "radix": false,
    "class-name": false,
    "eofline": false,
    "no-unused-expression": false,
    "no-console": false,
    "trailing-comma": false,
    "interface-name": false
  }
}
复制代码

自动修复功能

const { done } = require("@vue/cli-shared-utils");

module.exports = function lint({ args = {}, api, silent }) {
  const options = {
    fix: args.fix !== false,
    formatter: args.format || "codeFrame",
    formattersDirectory: args["formatters-dir"],
    rulesDirectory: args["rules-dir"]
  };

  const program = tslint.Linter.createProgram(api.resolve("tsconfig.json"));
  const linter = new tslint.Linter(options, program);

  const updateProgram = linter.updateProgram;
  linter.updateProgram = function(...args) {
    updateProgram.call(this, ...args);
    patchProgram(this.program);
  };

  const tslintConfigPath = tslint.Configuration.CONFIG_FILENAMES.map(filename =>
    api.resolve(filename)
  ).find(file => fs.existsSync(file));

  const config = tslint.Configuration.findConfiguration(tslintConfigPath)
    .results;

  const lint = file => {
    const filePath = api.resolve(file);
    const isVue = isVueFile(file);
    patchWriteFile();
    linter.lint(filePath, "", isVue ? vueConfig : config);
    restoreWriteFile();
  };

  const files =
    args._ && args._.length
      ? args._
      : [cwd + "/src/**/*.ts", cwd + "/src/**/*.vue", cwd + "/src/**/*.tsx"];

  return globby(files, { cwd }).then(files => {
    files.forEach(lint);
    if (silent) return;
    const result = linter.getResult();
    if (result.output.trim()) {
      process.stdout.write(result.output);
    } else if (result.fixes.length) {
      const f = new tslint.Formatters.ProseFormatter();
      process.stdout.write(f.format(result.failures, result.fixes));
    } else if (!result.failures.length) {
      done("tslint 没有发现错误.\n");
    }

    if (result.failures.length && !args.force) {
      process.exitCode = 1;
    }
  });
};
复制代码

提交检查

{
  "lint-staged": {
    "*.{vue,htm,html,css,sss,less,scss}": [
      "webpack-box lint stylelint",
      "git add"
    ],
    "*.{ts,tsx}": ["webpack-box lint tslint", "git add"],
    "*.{js,jsx}": ["webpack-box lint eslint", "git add"]
  }
}
复制代码

课题 13:配置别名

在咱们工做中,若是一个文件须要被 copy 到另一个目录下,那么这个文件的引用依赖就可能发生路径错误。还有咱们不喜欢每次引入依赖都要逐层去查找,咱们但愿可以有一个别名来指定某一个目录,不管咱们在哪里使用它。

本章概要

在项目中使用别名

src/main.js

import { cube } from "./treeShaking";
import { cube } from "@/treeShaking";
import { cube } from "@src/treeShaking";
复制代码

配置别名

alias: {
  '@': resolve('src'),
  '@src': resolve('src')
}
复制代码

webpack 实现

module.exports = ({ config, options, resolve }) => {
  const fs = require("fs");
  const conf = options.alias;
  return () => {
    // 生成默认别名
    const dirs = fs.readdirSync(resolve("src"));
    let aliasConfig = config.resolve.extensions
      .merge([".mjs", ".js", ".jsx", ".vue", ".ts", ".json", ".wasm"])
      .end().alias;
    dirs.forEach(v => {
      const stat = fs.statSync(resolve(`src/${v}`));
      if (stat.isDirectory()) {
        aliasConfig = aliasConfig.set(`@${v}`, resolve(`src/${v}`));
      }
    });

    // 用户配置别名
    if (conf.alias) {
      const keys = Object.keys(conf.alias);
      keys.forEach(key => {
        aliasConfig = aliasConfig.set(key, conf.alias[key]);
      });
    }

    // 自定义设置别名
    aliasConfig.set("@", resolve("src")).set("@src", resolve("src"));
  };
};
复制代码

编译器跳转配置

若是您使用的是 ts 的话,那么配置别名了以后会失去类型,提示找不到模块,因此咱们须要在编译器配置对应的别名才能够

tsconfig.json/jsconfig.json

{
  "compilerOptions": {
    "baseUrl": ".",
    "rootDir": ".",
    "paths": {
      "@src/*": [
        "src/*"
      ],
      "@/*": [
        "src/*"
      ],
    }
  }
}
复制代码

课时 15:定义通用变量

有时咱们须要在脚手架跟业务代码之间有一个通讯的桥梁

好比咱们 npm run build 时咱们是运行的生产环境,我想在 main.js 中生产环境下作一些特殊的逻辑。可是 main.js 执行是在浏览器端,而 npm run build 时运行在 node 端,两端没有办法作通信。那么咱们怎么办呢?

webpack 给咱们提供了一个插件 EnvironmentPlugin,这个插件能够将咱们在 node 端定义的变量,在编译时将值编译到代码中,举个例子

咱们在 main.js 中写了一段 node 中看起来很常见的代码,可是这在浏览器中是不能识别的,由于浏览器中并无 process 对象,这段代码不出意外会报错

main.js

if (process.env.NODE_ENV === "production") {
  console.log("Welcome to production");
}
复制代码

咱们配置 webpack.EnvironmentPlugin 插件

const webpack = require("webpack");

module.exports = ({ config, resolve, options }) => {
  return () => {
    const resolveClientEnv = require("../util/resolveClientEnv");
    config
      .plugin("process-env")
      .use(webpack.EnvironmentPlugin, [resolveClientEnv(options)]);
  };
};
复制代码

util/resolveClientEnv.js

module.exports = function resolveClientEnv(options, raw) {
  const env = {};
  if (process.env) {
    Object.keys(process.env).forEach(key => {
      if (key === "NODE_ENV") {
        env[key] = process.env[key];
      }
    });
  }
  if (options.env) {
    Object.assign(env, options.env);
  }
  return env;
};
复制代码

咱们执行 npm run build,看一下 dist/index.bundle.js 会编译成什么

// "production" === "production"
if (true) {
  console.log("Welcome to production");
}
复制代码

webpackprocess.env.NODE_ENV 的值编译在 bundle 中,这样咱们就能够在 web 端运行了,并且编译出来是在生产环境下

课时 16:严格区分路径大小写

有时咱们常常会出现这样的状况,明明本地编译没有问题,可是上线 jenkins 编译的时候就会报错,这种问题每每会花费咱们较长的时间才能发现这个 bug,原来是本地路径的大小写出现了问题,引用路径时咱们本地是不区分大小写的。举个例子

└──── src
   │── Index.js
   └── main.js
复制代码

上面的路径中 Index.js 的首字母是大写,可是我在 main.js 用小写去引用它

main.js

import Index from "./index.js";
复制代码

这样在本地是不会报错的,可是当你用 Jenkins 上线的时候,就会报错找不到 ./index.js 模块

因此咱们须要一个插件,在咱们开发时就严格检查大小写,这样就不会出现这样的问题了。

咱们使用 case-sensitive-paths-webpack-plugin 插件来实现它

module.exports = ({ config, webpackVersion, resolve, options }) => {
  return () => {
    // webpack 5 不兼容
    if (parseInt(webpackVersion) >= 5) return;
    config
      .plugin("case-sensitive-paths")
      .use(require("case-sensitive-paths-webpack-plugin"));
  };
};
复制代码

课时 17:加载资源 images、svg、media、fonts

这章就直接上代码吧,是以前基础篇的补充

module.exports = ({ config, webpackVersion, resolve, options }) => {
  return () => {
    const getAssetPath = require("../util/getAssetPath");
    const inlineLimit = 4096;

    const genAssetSubPath = dir => {
      return getAssetPath(
        options,
        `${dir}/[name]${options.filenameHashing ? ".[hash:8]" : ""}.[ext]`
      );
    };

    const genUrlLoaderOptions = dir => {
      return {
        limit: inlineLimit,
        fallback: {
          loader: "file-loader",
          options: {
            name: genAssetSubPath(dir)
          }
        }
      };
    };

    config.module
      .rule("images")
      .test(/\.(png|jpe?g|gif|webp)(\?.*)?$/)
      .use("url-loader")
      .loader(require.resolve("url-loader"))
      .options(genUrlLoaderOptions("img"));

    config.module
      .rule("svg")
      .test(/\.(svg)(\?.*)?$/)
      .use("file-loader")
      .loader(require.resolve("file-loader"))
      .options({
        name: genAssetSubPath("img")
      });

    config.module
      .rule("media")
      .test(/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/)
      .use("url-loader")
      .loader(require.resolve("url-loader"))
      .options(genUrlLoaderOptions("media"));

    config.module
      .rule("fonts")
      .test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/i)
      .use("url-loader")
      .loader(require.resolve("url-loader"))
      .options(genUrlLoaderOptions("fonts"));
  };
};
复制代码

课时 18:设置全局样式

在书写 css 时,咱们会将经常使用到的函数/变量等封装成一个 global.less/scss,而后在咱们用到的时候将其引入。显然每次都要手动引入变得很麻烦,也容易出错(尤为组内来新人的时候),因此咱们想若是把 global 自动引入到文件中不就完美了吗?

咱们须要一个 style-resources-loader 来帮助咱们完成这件事

配置 style-resources-loader

config/styleResourceLoader.js

module.exports = ({ config, options }) => {
  const resourcesOpt = options.resources;
  return () => {
    ["normal"].forEach(oneOf => {
      Object.keys(resourcesOpt).forEach(loader => {
        config.module
          .rule(loader)
          .oneOf(oneOf)
          .use("style-resources-loader")
          .loader("style-resources-loader")
          .options({
            patterns: resourcesOpt[loader].patterns
          });
      });
    });
  };
};
复制代码

config/style.js

if (loader) {
  let resolvedLoader;
  try {
    resolvedLoader = require.resolve(loader);
  } catch (error) {
    resolvedLoader = loader;
  }
  rule
    .use(loader)
    .loader(resolvedLoader)
    // options 是对应 config 中的 css 参数,能够自行配置对应loader的参数
    .options(Object.assign({ sourceMap }, options));
}
复制代码

项目配置

box.config.js

{
  "css": {
    "sourceMap": true,  // 是否开启css source map
    "loaderOptions": { // 配置loader的options
      "css": {},
      "less": {
        "globalVars": { // less 设置全局变量
          "gray": "#ccc"
        }
      },
      "sass": {},
      "postcss": {},
      "stylus": {}
    },
    "isCssModule": false, // 是否对css进行模块化处理
    "needInlineMinification": false // 是否须要压缩css
  },
  "resources": {
    "less": {
      "patterns": [path.resolve(__dirname, "./src/global/*.less")]
    },
    "scss": {
      "patterns": [path.resolve(__dirname, "./src/global/*.scss")]
    }
  }
}
复制代码

使用

└──── src
   │── global
   │  │── index.less
   │  └── index.scss
   └── style
      └── index.less
复制代码

设置全局样式

global/index.less

.g-less-height () {
  height: 100%;
}

.g-less-test {
  width: 100%;
}
复制代码

使用全局样式

style/index.less

.test {
  width: 300px;
  color: @gray;
  .g-less-height();
}
复制代码

style/index.scss

.g-scss-test {
  width: 100%;
}
复制代码

编译后

dist/css/index.css

.g-less-test {
  width: 100%;
}

.test {
  color: #ccc;
  width: 100%;
  height: 100%;
}

.g-scss-test {
  width: 100%;
}
复制代码

可见全局的样式都被打包进 dist/css/index.css 中了,咱们不再用每次都手动引入了

vscode 配置

放在根目录,开启自动修复 eslint/tslint/stylelint 功能

.vscode/setting.json

{
  /* * @description 编译器配置 * @param tabSize 默认tab为两个空格 * @param formatOnSave 保存时自动修复 */
  "editor.tabSize": 2,
  "editor.formatOnSave": true,
  /* * @description eslint 配置 * @param alwaysShowStatus 配置 * @param autoFixOnSave 保存时自动修复 * @param validate 在vue中添加错误提示 */
  "eslint.alwaysShowStatus": true,
  "eslint.autoFixOnSave": true,
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    {
      "language": "vue",
      "autoFix": true
    }
  ],
  /* * @description tslint 配置 * @param autoFixOnSave 保存时自动修复 * @param alwaysShowRuleFailuresAsWarnings 全部特征都是用 Warnings */
  "tslint.autoFixOnSave":  true,
  "tslint.alwaysShowRuleFailuresAsWarnings": true,
  /* * @description stylelint 配置 * @param autoFixOnSave 保存时自动修复 */
  "stylelint.autoFixOnSave":  true,
  /* * @description vetur 配置 */
  "vetur.format.defaultFormatter.html": "prettier",
  "vetur.format.defaultFormatterOptions": {
    "prettier": {
      "semi": false,
      "singleQuote": true
    }
  },
  /* * @description 配置编辑器设置以覆盖某种语言 */
  "[typescript]": {
    // "editor.defaultFormatter": "esbenp.prettier-vscode"
    "editor.defaultFormatter": "eg2.tslint"
  },
  "[html]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[jsonc]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[vue]": {
    "editor.defaultFormatter": "eg2.tslint"
  },
  "[javascript]": {
    "editor.defaultFormatter": "dbaeumer.vscode-eslint"
  },
  "[javascriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }
}
复制代码

prettier 配置

配合 lint 作一些,代码样式上的格式化

prettier.config.js

/** * pretiier 标准配置 */
module.exports = {
  // 在ES5中有效的结尾逗号(对象,数组等)
  trailingComma: "es5",
  // 不使用缩进符,而使用空格
  useTabs: false,
  // tab 用两个空格代替
  tabWidth: 2,
  // 仅在语法可能出现错误的时候才会添加分号
  semi: false,
  // 使用单引号
  singleQuote: true,
  // 在Vue文件中缩进脚本和样式标签。
  vueIndentScriptAndStyle: true,
  // 一行最多 100 字符
  printWidth: 100,
  // 对象的 key 仅在必要时用引号
  quoteProps: "as-needed",
  // jsx 不使用单引号,而使用双引号
  jsxSingleQuote: false,
  // 大括号内的首尾须要空格
  bracketSpacing: true,
  // jsx 标签的反尖括号须要换行
  jsxBracketSameLine: false,
  // 箭头函数,只有一个参数的时候,也须要括号
  arrowParens: "always",
  // 每一个文件格式化的范围是文件的所有内容
  rangeStart: 0,
  rangeEnd: Infinity,
  // 不须要写文件开头的 @prettier
  requirePragma: false,
  // 不须要自动在文件开头插入 @prettier
  insertPragma: false,
  // 使用默认的折行标准
  proseWrap: "preserve",
  // 根据显示样式决定 html 要不要折行
  htmlWhitespaceSensitivity: "css",
  // 换行符使用 lf
  endOfLine: "lf"
};
复制代码

总结

至此webpack系列2已经结束了,更精彩的还在后面,前面两篇文章只能算是为后来的大项目作铺垫,后续我会使用lerna进行重构,将使用插件化管理,构建插件化生态令人人有益。后面会构建vue跟react项目,甚至小程序,跨端等都会被集成进来。

您能够将您想的想法提到这里面 github.com/luoxue-vict… 我会认真对待每个issue

最后感谢你们的支持!我叫技匠能够微信公众号搜索前端技匠,也能够加我微信 luoxue2479 进群交流。

相关文章
相关标签/搜索