从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



相关文章
|
26天前
|
缓存 监控
webpack 提高构建速度的方式
【10月更文挑战第23天】需要根据项目的具体情况和需求,综合运用这些方法,不断进行优化和改进,以达到最佳的构建速度和效果。同时,随着项目的发展和变化,还需要持续关注和调整构建速度的相关措施,以适应不断变化的需求。
|
29天前
|
前端开发 JavaScript
手敲Webpack 5:React + TypeScript项目脚手架搭建实践
手敲Webpack 5:React + TypeScript项目脚手架搭建实践
|
26天前
|
存储 缓存 前端开发
利用 Webpack 5 的持久化缓存来提高构建效率
【10月更文挑战第23天】利用 Webpack 5 的持久化缓存是提高构建效率的有效手段。通过合理的配置和管理,我们可以充分发挥缓存的优势,为项目的构建和开发带来更大的便利和效率提升。你可以根据项目的实际情况,结合以上步骤和方法,进一步优化和完善利用持久化缓存的策略,以达到最佳的构建效果。同时,不断探索和实践新的方法和技术,以适应不断变化的前端开发环境和需求。
|
3月前
webpack——通过webpack-bundle-analyzer分析项目包占比情况
webpack——通过webpack-bundle-analyzer分析项目包占比情况
37 2
webpack——通过webpack-bundle-analyzer分析项目包占比情况
|
3月前
|
缓存 JSON JavaScript
简单介绍下从零搭建 Webpack 项目
本文详细介绍了Webpack中Loader的概念及其重要性。Webpack仅支持处理JS和JSON文件,而Loader能够帮助处理其他类型的文件,如CSS、图片等,并将其转换为有效的模块。文章首先解释了Loader的基本原理,接着介绍了几种常见Loader的配置和使用方法
23 1
|
3月前
|
前端开发 JavaScript API
|
3月前
|
前端开发 JavaScript C++
【绝技大公开】Webpack VS Rollup:一场前端工程化领域的巅峰对决,谁能笑到最后?——揭秘两大构建神器背后的秘密与奇迹!
【8月更文挑战第12天】随着前端技术的发展,模块化与自动化构建成为标准实践。Webpack与Rollup作为主流构建工具,各具特色。Webpack是一款全能型打包器,能处理多种静态资源,配置灵活,适合复杂项目;Rollup专注于ES6模块打包,利用Tree Shaking技术减少冗余,生成更精简的代码。Rollup构建速度快,配置简洁,而Webpack则拥有更丰富的插件生态系统。选择合适的工具需根据项目需求和个人偏好决定。两者都能有效提升前端工程化水平,助力高质量应用开发。
45 1
|
3月前
|
JavaScript 前端开发 API
解锁前端开发新境界:Vue.js携手Webpack,打造高效构建流程,你的项目值得拥有!
【8月更文挑战第30天】随着前端技术的发展,模块化与组件化趋势愈发显著。Vue.js 以其简洁的 API 和灵活的组件系统,深受开发者喜爱;Webpack 则凭借强大的模块打包能力成为前端工程化的基石。两者结合,不仅简化了组件编写与引用,还通过模块热替换、代码分割等功能大幅提升开发效率。本文将通过具体示例,展示如何利用 Vue.js 和 Webpack 构建高效、有序的前端开发环境。从安装配置到实际应用,逐步解析这一组合的优势所在。
49 0
|
3月前
|
JavaScript 测试技术
在不同 webpack 版本的 Vue 项目中配置 Storybook
在不同 webpack 版本的 Vue 项目中配置 Storybook
|
2月前
|
JavaScript
webpack打包TS
webpack打包TS
133 60
下一篇
无影云桌面