从0到1带你用webpack 5构建monorepo项目——上篇(一)

简介: 前言大家好,我是Fly哥,继续上一次 搭建 monorepo 仓库发文已经 很久了, 这是工程化系列的第二篇, 不熟悉的同学可以看下上一篇文章10分钟带你从0到1搭建monorepo 工程化项目 这一篇文章是延续上一篇文章, 从0-1带你搭建 大型 react 项目。读完本篇文章你可以学到如下, 如果你会了,直接跳过别浪费时间,预计阅读 15分钟。tsx 的 编译的两种方式 babel 和 ts-loader基于webpack 5 的 前端构建策略

前言



大家好,我是Fly哥,继续上一次 搭建 monorepo 仓库发文已经 很久了, 这是工程化系列的第二篇, 不熟悉的同学可以看下上一篇文章10分钟带你从0到1搭建monorepo 工程化项目  这一篇文章是延续上一篇文章, 从0-1带你搭建 大型 react 项目。读完本篇文章你可以学到如下, 如果你会了,直接跳过别浪费时间,预计阅读 15分钟。


  1. tsx 的 编译的两种方式  babel  和 ts-loader


  1. 基于webpack 5 的 前端构建策略


安装webpack



我们先在项目中 安装  webpack 和 react  和 react-dom


yarn add -D -W webpack webpack-cli
yarn add  react react-dom


然后我们在项目 中定义一个 文件  webpack.dev.js  然后我们在package.json 定义一个


开发环境的打包脚本


"dev": "webpack serve --config  ./scripts/webpack.dev.js"


这段脚本的意思就是 webpack 的打包 方式通过 配置文件  所以 我们 定义一下我们

webpack 的配置。  webpack的配置文件 默认是 commonjs 的导出方式  , 如果是用

esm 的方式 是不可以的, 但是如果你是用 cli , 可以都用 esm 的方式 。


mode: 'development',
  devtool: 'inline-source-map',
  entry: path.join(rootPath, '3d/src/index.tsx'),
  output: {
    filename: 'bundle.js',
    clean: true, // 每次打包之前 清空 dist目录
  },


我们定义了 入口 文件 ,以及出口文件, clean: true   在webpack 5 我们不要安装插件

了 去清除每一次的dist 目录,webpack 5 默认都是支持的 。这样我们直接执行 去打包 是 会直接报错的。


原因:「webpack  他不认识 以 tsx 结尾的文件」, 所以就需要我们经常 所说的 loader , loader 的作用 就是 将 tsx 结尾的文件, 编译webpack 能够 认识的 文件 也就是 js。


社区中对与 tsx 的编译方式 其实 有2种


  1. 第一种 使用 ts-loader 去进行 tsx 的 打包


  1. 第二种 就是 使用 babel 对我们 对于我们的 tsx 进行打包


  1. 第三种 就是 ts-laoder  和  babel-loader 的结合


ts-loader



这里的话我们先用ts-loader 进行打包 首先先安装


yarn add -D -w ts-loader


然后我们进行以下配置:


{
  test: /\.tsx?$/,
  use: [
    {
      loader: 'ts-loader',
      options: {
        transpileOnly: true,
        happyPackMode: true,
        configFile: join(rootDir, 'tsconfig.json'),
        experimentalWatchApi: true,
        getCustomTransformers: join(__dirname, 'ts-transformers'),
      },
    },
  ],
  exclude: /node_modules/,
},


「transpileOnly:」 表示 我们只进行编译不进行 类型检查, 不然项目越大,开发环境体验太差了。如果你需要,进行类型检查, 可以 配合这个webpack 插件 进行 使用


yarn add -D -W fork-ts-checker-webpack-plugin


然后我们在webpack 进行配置如下:


const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
plugins: [
    new ForkTsCheckerWebpackPlugin(),
],


「HappyPackMode:」 表示开启多进程, 或者 可以使用  thread-loader 都可以


「configFile」:你项目ts-config.json 的文件 , 我们这里就是跟目录的  ts 配置文件


「getCustomTransformers:」 这个其实就是 自定义一些 转换tsx ,举个常见的例子, 就是 ts-loader 如何 实现 按需加载, 下面的文章内容我使用babel 配和 babel-plugin-import 实现了, 但是「如果你使用 ts-loader 进行 如何实现按需加载 ???」


我们看下这个接口类型:


(program: Program, getProgram: () => Program) => { before?: TransformerFactory<SourceFile>[]; after?: TransformerFactory<SourceFile>[]; afterDeclarations?: TransformerFactory<SourceFile>[]; }


按需加载 其实就是对编译 tsx 的 过程中 ,给我们 提供了 3个钩子, 而按需加载, 对于 生成的 AST 语法树 进行改写, 转换成 按需加载的引用方式, 而你 自己根本不需要关心。社区里面已经有轮子, 替我们搞定了 。


yarn add ts-import-plugin -D -W


webpack 进行以下配置


getCustomTransformers: () => ({
    before: [ tsImportPluginFactory( [{
        libraryName:'antd',
        libraryDirectory:'lib',
        style:true  
    }]) ]
}),


其实和 babel-plugin-import  整体使用的方式差不多的。


如果项目中使用 babel 进行编译, 那就 以babel 的方式 去编译 tsx ,这两个其实 都可以 ,但是 个人倾向于 babel 进行编译, 毕竟可扩展性更强, 你可以编写babel  插件 ,或者一些新的语法。下面我们就来介绍使用babel 进行编译


babel



这时候我们用「babel-loader」  的方式 去对 tsx 进行打包。

我们先安装下面几个包

yarn add -D -W @babel/core @babel/preset-env babel-loader @babel/plugin-transform-runtime @babel/preset-react  babel-loader


我一个个去解释每一个包是干什么用的


babel-loader


babel-loader 首先对于我们项目中的jsx 文件, 需要通过「jsx」 文件转换为「js」 文件, 但是「babel-loader」 这里起到的作用可能就是转换器。


@babel/core


但是babel-loader仅仅识别出了jsx文件,内部核心转译功能需要@babel/core这个核心库,@babel/core模块就是负责内部核心转译实现的。


@babel/preset-env


@babel/preset-env是一个智能预设,允许您使用最新的 JavaScript,而无需微观管理目标环境需要哪些语法转换(以及可选的浏览器 polyfill)。这既让你的生活更轻松,也让 JavaScript 包更小!


@babel/prest-envbabel转译过程中的一些预设,它负责将一些基础的es 6+语法,比如const/let...转译成为浏览器可以识别的低级别兼容性语法。同时在打包的过程中, 通过配置进行按需引用, 后面参数我会讲解,但是一些高级别模块


polyfill)的实现还是不支持的,这时候我们就需要 下面一个 npm 包


@babel/plugin-transform-runtime


@babel/plugin-transform-runtime,上边我们提到了对于一些高版本内置模块,比如Promise/Generate等等@babel/preset-env并不会转化,所以@babel/plugin-transform-runtime就是帮助我们来实现这样的效果的,他会在我们项目中如果使用到了Promise之类的模块之后去实现一个低版本浏览器的polyfill


@babel/preset-react


我们希望将.jsx文件转化为js文件同时将jsx标签转化为React.createElement的形式,此时我们就需要额外使用babel的另一个插件-@babel/preset-react


@babel/preset-typescript


babel内置了一组预设去转译TypeScript代码 --@babel/preset-typescript。接下来,我们进行babel 的配置呗,


{
        // 同时认识ts jsx js tsx 文件
        test: /\.(t|j)sx?$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              [
                '@babel/preset-env',
                {
                  useBuiltIns: 'usage',
                  corejs: 3,
                },
              ],
              '@babel/preset-react',
              '@babel/preset-typescript',
            ],
            plugins: [
              [
                '@babel/plugin-transform-runtime',
                {
                  regenerator: true,
                },
              ],
            ],
          },
        },
      },


这里主要给大家介绍下这两个参数


  1. 「useBuiltIns」:无论你选择 usage 还是 entry  , babel 都依赖 core-js 这个 包,因为 @babel/polyfill 已经被废弃, 默认是core-js 2.0 版本, 你可以选择3.0版本


yarn add core-js@2 -D -W  || yarn add core-js@3 -D -W


我们看下我们的测试文件:


import ReactDom from 'react-dom'
class A {
  static async wait() {
    return Promise.resolve(5)
  }
  ctx?: number
  constructor(ctx?: number) {
    this.ctx = ctx ?? 5
  }
}
ReactDom.render(<div>{`${new A('2').ctx}我是测试的`}</div>, document.getElementById('root'))


然后我们执行 打包命令:yarn build

image.png

image.gifsuc


但是用babel-loader 打包tsx 文件的时候, 做不到类型检查, 细心的同学可以发现 我传一个了字符串 进去, 按道理我们在打包之前就应该做一次类型检查。这时候有同学就会问, babel 支持ts 的类型检查???


「答案是不可以」


因为 tsc 的类型检查是需要拿到整个工程的类型信息,需要做类型的引入、多个文件的 namespace、enum、interface 等的合并,而 babel 是单个文件编译的,不会解析其他文件的信息。所以做不到和 tsc 一样的类型检查。


「一个是在编译过程中解析多个文件,一个是编译过程只针对单个文件,流程上的不同,导致 babel 无法做 tsc 的类型检查。」


那我们怎么去解决呢或者去限制呢


1.我们可以使用 tsc的类型检查 但是不去进行编译, 我们用 tsc +  babel 的方式去进行打包


"build": "yarn check && webpack --config ./scripts/webpack.base.ts",
 "check": "tsc --noEmit",


2.我们使用webpack插件 去做类型检查


yarn add fork-ts-checker-webpack-plugin -D -W


然后我们在插件处进行配置。

image.png

image.gifwebpack


然后我们执行build


image.png


error


检查出错误了。


monorepo 项目babel配置


这里做一个小提醒, 在monorepo 项目中  如果想将babel 的配置提取出来,我一开始创建的是 .babelrc 文件 然后把配置放进去, 经过查阅官方资料, 对于 monorepo 项目  更加推荐在项目


任何 monorepo 结构的第一步应该是babel.config.json 在存储库根目录中创建一个文件。这确立了 Babel 的核心概念,即存储库的基本目录。即使您想使

.babelrc.json文件来配置每个单独的包,重要的是要有一个存储库级别选项的位置。


您通常可以将所有 repo 配置放在 root 中babel.config.json。使用"overrides".babelrc.json ,您可以轻松地指定仅适用于存储库的某些子文件夹的配置,这通常比跨存储库创建许多文件更容易遵循。


您可能会遇到的第一个问题是,默认情况下,Babel 期望babel.config.json 从设置为其"root"的目录加载文件,这意味着如果您创建一个babel.config.json,但在单个包中运行 Babel,例如


cd packages/some-package;
babel src -d dist


在该上下文中使用的“根” Babel不是您的 monorepo 根,它无法找到该babel.config.json文件。


如果你的所有构建脚本都相对于你的存储库根目录运行,那么一切应该已经工作了,但是如果你是从一个子包中运行你的 Babel 编译过程,你需要告诉 Babel 在哪里寻找配置。有几种方法可以做到这一点,但推荐的方法是使用"rootMode"选项"upward",这将使 Babel 从工作目录向上搜索您的babel.config.json文件,并将其位置用作"root"值。


babel缓存配置


首先在babel-loader 后面加上 ?cacheDirectory ,  缓存每一个babel转译的结果


{
    // 同时认识ts jsx js tsx 文件
    test: /\.(t|j)sx?$/,
    exclude: /node_modules/,
    use: {
      loader: 'babel-loader?cacheDirectory',
    },
  },


同时在babel.config.js 增加 如下代码


module.exports = function (api) {
  // 这里 我们根据api 做一些自定义的webpack 打包配置, 比如根据环境
  // 根据node 和 web 做一些不同的东西
  return {
    presets: [
      [
        '@babel/preset-env',
        {
          useBuiltIns: 'usage',
          corejs: 3,
            // caller.target 等于 webpack 配置的 target 选项
          targets: api.caller((caller) => caller && caller.target === 'node')
            ? { node: 'current' }
            : { chrome: '58', ie: '11' },
        },
      ],
      [
        '@babel/preset-typescript',
        {
          isTSX: true,
          allExtensions: true,
        },
      ],
      '@babel/preset-react',
    ],
    plugins: [
      [
        '@babel/plugin-transform-runtime',
        {
          regenerator: true,
        },
      ],
    ],
    ignore: ['dist', 'node_modules'],
  }
}


资源配置



资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader。


在 webpack 5 之前,通常使用:


  • raw-loader 将文件导入为字符串


  • url-loader 将文件作为 data URI 内联到 bundle 中


  • file-loader 将文件发送到输出目录


资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:


  • asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。


  • asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。


  • asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。


  • asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。


当在 webpack 5 中使用旧的 assets loader(如 file-loader/url-loader/raw-loader 等)和 asset 模块时,你可能想停止当前 asset 模块的处理,并再次启动处理,这可能会导致 asset 重复,你可以通过将 asset 模块的类型设置为

'javascript/auto' 来解决。


然后我的 webpack 配置如下


{
        test: /\.(png|jpg|gif|svg)$/,
        type: 'asset/resource',
      },
      {
        test: /\.(ttf|eot|woff|woff2)$/i,
        type: 'asset/resource',
        generator: {
          filename: 'assets/[name].[contenthash:8].[ext]',
        },
      },
      {
        test: /\.txt$/,
        type: 'asset/source',
      },


然后我们在项目中重新引入 一张图片 会出现下面这种情况

image.png

image.gifimage-20220502171617345


其实就tsx, 找不到 这个模块, 我们在 monorepo 根目录 新建一个  types ,用来放置一些 全局的声明模块


image.png


image-20220502171829638


然后在里面加入


declare module '*.css'
declare module '*.png'
declare module '*.scss'
declare module '*.svg'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
declare module '*.tiff'


这个时候还不行, 我们切换到子目录的 tsConfig 中, 然后在 include 文件 中 加入 当前 文件路径, 这样就不会报错了


{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["./src/**/*", "../../types/index.d.ts"]
}


这样就可以了, 就不会报类型错误了。


我们看一下打包生成的图片文件


image.png


image-20220502184002320


其实我们看了下主要在 dist 目录, 对于资源的配置我们其实也可以自定义图片的输出路径  有下面两种方法


  1. 第一个是在 webpack  output 输出 配置下面下这段代码:


assetModuleFilename: 'images/[hash][ext][query]',


2 .  或者是在自定义输出目录如下:


{
        test: /\.(png|jpg|gif|svg)$/,
        type: 'asset/resource',
        generator: {
          filename: 'images/[name].[contenthash:8][ext]',
        },
      },


这两种方式其实是等效的,但是自定义资源目录。但是第二种方式仅适用于 assetasset/resource 模块类型。


我们看下打包结果:


image.gifimage-20220502184828504



相关文章
|
6天前
|
前端开发
webpack如何设置devServer启动项目为https协议
webpack如何设置devServer启动项目为https协议
201 0
|
6天前
|
缓存 前端开发 JavaScript
「零基础」掌握Webpack:10个必学技巧,为前端项目保驾护航!(三)
「零基础」掌握Webpack:10个必学技巧,为前端项目保驾护航!
|
6天前
|
缓存 JavaScript 前端开发
【TypeScript技术专栏】TypeScript与Webpack构建优化
【4月更文挑战第30天】本文探讨了优化TypeScript与Webpack构建性能的策略。理解Webpack的解析、构建和生成阶段是关键。优化包括:调整tsconfig.json(关闭不必要的类型检查,适配目标环境)和webpack.config.js(配置entry、output、resolve,使用压缩插件)。启用Webpack缓存和增量构建,利用代码拆分与懒加载,能有效提升构建速度和开发效率。
|
6天前
|
JavaScript 前端开发
构建工具:配置Webpack打包Vue项目
【4月更文挑战第24天】本文介绍了如何配置Webpack来打包Vue项目。首先,Webpack作为模块打包器处理依赖并打包成可执行文件。接着,通过安装Node.js和npm,创建Vue项目,进入项目目录并配置Webpack的入口、输出、加载器和插件。最后,运行构建命令完成打包。理解Webpack基础并按需配置,能优化前端项目构建和开发体验。
|
6天前
|
缓存 JavaScript 前端开发
js开发:请解释什么是Webpack,以及它在项目中的作用。
Webpack是开源的JavaScript模块打包器,用于前端项目构建,整合并优化JavaScript、CSS、图片等资源。它实现模块打包、代码分割以提升加载速度,同时进行资源优化和缓存。Webpack的插件机制可扩展功能,支持热更新以加速开发流程。
21 2
|
6天前
|
缓存 前端开发 JavaScript
|
6天前
|
前端开发 JavaScript 安全
|
6天前
|
前端开发 JavaScript Java
|
6天前
|
缓存 前端开发 JavaScript
「零基础」掌握Webpack:10个必学技巧,为前端项目保驾护航!(二)
「零基础」掌握Webpack:10个必学技巧,为前端项目保驾护航!
|
6天前
|
前端开发 JavaScript
「零基础」掌握Webpack:10个必学技巧,为前端项目保驾护航!(一)
「零基础」掌握Webpack:10个必学技巧,为前端项目保驾护航!