社区精选 |Wuzzle,不 eject 也能定制 create-react-app 创建的 React 应用

业界 作者:SegmentFault 2022-08-01 13:44:49

今天小编为大家带来的是社区作者 乌柏木 的文章,在这篇文章看看他如何通过 wuzzle,能够在不 eject 的情况下任意定制 CRA 创建的 React 应用。




作为 React 官方维护的命令行工具,create-react-app(简称 CRA)能够极其轻松地创建配置完备的 React 应用,帮助使用者快速进入 React 开发。它的最大缺憾是创建的应用不能随心所欲地定制配置。想要定制,只能 eject。而 eject 就意味着应用所有的配置都交由使用者维护,繁琐令人望而却步。


CRA链接: https://github.com/facebook/create-react-app


现在,通过 wuzzle,我们能够在不 eject 的情况下任意定制 CRA 创建的 React 应用。


wuzzle链接:https://github.com/host1-tech/wuzzle


不 eject 的情况下查看 CRA webpack 配置



首先,用 CRA 创建一个支持 TypeScript 的演示应用(如果不习惯用 TS 去掉参数 --template typescript 即可):


$ npx create-react-app --template typescript demo
# ...
cd demo


安装 wuzzle:

$ npm i -D wuzzle


打开 package.json 编辑 scripts 挂载 wuzzle:


  "scripts": {
-    "start""react-scripts start",
+    "start""wuzzle react-scripts start",
-    "build""react-scripts build",
+    "build""wuzzle react-scripts build",
  },


现在,通过参数 --dry-run 运行 start 或 build 脚本就可以直接查看 CRA 内部使用的 webpack 配置了:


$ npm run build -- --dry-run
# ...
@wuzzle/cli:applyConfig Webpack config with difference: {
  # ...
  devtool: # ...
  entry: # ...
  output: #...
  cache: #...
  resolve: # ...
  module: # ...
  plugins: # ...
  # ...
}


不 eject 的情况下引入 less、使用 antd



在样式文件上,CRA 应用支持 css、scss/sass,但不支持 less。想要全面使用 antd 并做主题修改,需要在 webpack 配置引入 less。回到 --dry-run 运行结果细看一下 module 字段:


$ npm run build -- --dry-run
# ...
@wuzzle/cli:applyConfig Webpack config with difference: {
  # ...
  module: {
    # ...
    rules: [
      # ...
      {
        oneOf: [
          # ...
          {
            test: /\.(scss|sass)$/,
            exclude: /\.module\.(scss|sass)$/,
            use: [
              {
                loader: '.../mini-css-extract-plugin/dist/loader.js',
                options: {}
              },
              {
                loader: '.../css-loader/...',
                options: # ...
              },
              {
                loader: '.../postcss-loader/...',
                options: # ...
              },
              {
                loader: '.../resolve-url-loader/...',
                options: # ...
              },
              {
                loader: '.../sass-loader/...',
                options: # ...
              }
            ],
          },
          {
            test: /\.module\.(scss|sass)$/,
            use: [
              {
                loader: '.../mini-css-extract-plugin/dist/loader.js',
                options: # ...
              },
              {
                loader: '.../css-loader/...',
                options: # ...
              },
              {
                loader: '.../postcss-loader/...',
                options: # ...
              },
              {
                loader: '.../resolve-url-loader/...',
                options: # ...
              },
              {
                loader: '.../sass-loader/...',
                options: # ...
              }
            ]
          },
          # ...
        ]
      }
    ]
  },
  # ...
}


不难发现,sass 的配置方法和 less 的很接近,只要稍加改造,把 sass-loader 替换成 less-loader 并去掉 resolve-url-loader 就达成目标了。


安装一下配置 less 所需的依赖:


npm i -D less less-loader


然后,在 package.json 旁创建文件 wuzzle.config.js 修改 CRA 内部使用的 webpack 配置,这里可以使用 wuzzle 提供的修改帮助方法减轻工作量:


修改方法:https://github.com/host1-tech/wuzzle/tree/v0.6.3#modification-utilities-on-webpack-configs

const appPaths = require('react-scripts/config/paths');
const { deleteUseItem, firstRule, firstUseItem, replaceUseItem } = require('wuzzle');

module.exports = (webpackConfig, webpack, wuzzleContext) => {
  const { commandArgs } = wuzzleContext;

  if (commandArgs[0] === 'start' || commandArgs[0] === 'build') {
    // Replace sass-loader with less-loader to support .less files
    const lessOptions = { javascriptEnabled: true };

    const scssRuleQuery = { file: { dir: appPaths.appSrc, base: 'index.scss' } };
    const lessRule = firstRule(webpackConfig, scssRuleQuery);
    Object.assign(lessRule, { test: /\.(less)$/, exclude: /\.module\.less$/ });
    deleteUseItem(lessRule, { loader: 'resolve-url-loader' });
    replaceUseItem(
      lessRule,
      { loader: 'sass-loader' },
      { loader: 'less-loader', options: { sourceMap: true, lessOptions } }
    );
    firstUseItem(lessRule, { loader: 'css-loader' }).options.importLoaders = 2;

    const scssModuleRuleQuery = { file: { dir: appPaths.appSrc, base: 'index.module.scss' } };

    const lessModuleRule = firstRule(webpackConfig, scssModuleRuleQuery);
    Object.assign(lessModuleRule, { test: /\.module\.less$/ });
    deleteUseItem(lessModuleRule, { loader: 'resolve-url-loader' });
    replaceUseItem(
      lessModuleRule,
      { loader: 'sass-loader' },
      { loader: 'less-loader', options: { sourceMap: true, lessOptions } }
    );
    firstUseItem(lessModuleRule, { loader: 'css-loader' }).options.importLoaders = 2;
  }
};


之后,把所有的 .css 文件后缀改为 .less


  • 把 index.css 重命名为 index.less,在 index.tsx 把 import './index.css'; 改为 import './index.less';
  •  App.css 重命名为 App.less,在 App.tsx 把 import './App.css'; 改为 import './App.less';

安装一下 antd:

$ npm i -S antd


在 index.less 引入 antd 样式文件:


-body {
-  ...
-}
-code {
-  ...
-}
+@import '~antd/dist/antd.less';


如果想要修改 antd 主题的话,可以回到 wuzzle.config.js  lessOptions 中添加 modifyVars 字段,比如:



-    const lessOptions = { javascriptEnabled: true };
+    const lessOptions = {
+      javascriptEnabled: true,
+      modifyVars: { '@primary-color''#1da57a' },
+    };


现在,运行 start 或 build 脚本就可以看到在 CRA 应用中引入 less、使用 antd 的效果了:

$ npm start
Starting the development server...
Compiled successfully!

You can now view demo in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://192.168.100.24:3000

Note that the development build is not optimized.
To create a production build, use npm run build.

webpack compiled successfully
No issues found.


不 eject 的情况下调整 CRA test 配置



CRA test 内部是基于 jest 封装的,不是 webpack。对于 jest,wuzzle 提供了两种定制配置的方法:

  1. 以兼容的 wepback 编译替代 jest 自身的编译,使用者修改 webpack 编译配置。
  2. 继续使用 jest 自己的编译,使用者修改 jest 编译配置。

分别看下如何通过这两种方法引入 less 保持 test 脚本兼容。

方法 1


回到 package.json,编辑 scripts  test 脚本挂载上 wuzzle

  "scripts": {
-    "test""react-scripts test",
+    "test""wuzzle react-scripts test",
  },


然后,通过参数 --dry-run 运行 test 脚本查看替代 jest 自身编译的 webpack 编译的配置:

$ npm test -- --dry-run
# ...
@wuzzle/cli:applyConfig Webpack config with difference: {
  # ...
  module: {
    rules: [
      {
        test: /\.(js|jsx|mjs|cjs|ts|tsx)$/,
        exclude: /node_modules/,
        use: # ...
      },
      {
        test: /\.css$/,
        exclude: /node_modules/,
        use: [
          {
            loader: '.../null-loader/...'
          }
        ]
      },
      {
        test: /\.svg$/,
        exclude: /node_modules/,
        use: # ...
      },
      {
        exclude: [ /\.(js|jsx|mjs|cjs|ts|tsx|json|css|svg)$/, /node_modules/ ],
        use: # ...
      }
    ]
  },
  # ...
}


可以发现,只要在 css 配置中添加对 .less 文件的匹配、在兜底配置中去掉对 .less 文件的匹配就可以了。

回到 wuzzle.config.js,修改替代 jest 自身编译的 webpack 编译的配置:

module.exports = (webpackConfig, webpack, wuzzleContext) => {
  const { commandArgs } = wuzzleContext;

  // ...

+  if (commandArgs[0] === 'test') {
+    const cssRule = firstRule(webpackConfig, { file: 'index.css' });
+    cssRule.test = [cssRule.test, /\.less$/];
+
+    const fallbackRule = firstRule(webpackConfig, { file: 'index.fallback' });
+    fallbackRule.exclude.push(/\.less$/);
+  }
};

现在,运行 test 脚本就可以看到 CRA test 中引入 less 的效果了:

$ npm test
# ...
File 'src/setupTests.ts' compiled.
File 'src/App.test.tsx' compiled.
File 'src/App.tsx' compiled.
File 'src/App.less' compiled.
File 'src/logo.svg' compiled.
 PASS  src/App.test.tsx (10.183 s)
  ✓ renders learn react link (40 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        11.005 s
Ran all test suites related to changed files.


方法 2


回到 package.json,再次编辑 scriptstest 脚本添加参数 --no-webpack 关闭 webpack 编译:

  "scripts": {
-    "test""wuzzle react-scripts test",
+    "test""wuzzle react-scripts test --no-webpack",
  },


然后,通过参数 --dry-run 运行 test 脚本查看 jest 自身编译的配置:

$ npm test -- --dry-run
# ...
@wuzzle/cli:applyConfig Jest config with difference: {
  # ...
  transform: [
    [
      '^.+\\.(js|jsx|mjs|cjs|ts|tsx)$',
      '.../node_modules/react-scripts/config/jest/babelTransform.js',
      {}
    ],
    [
      '^.+\\.css$',
      '.../node_modules/react-scripts/config/jest/cssTransform.js',
      {}
    ],
    [
      '^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)',
      '.../node_modules/react-scripts/config/jest/fileTransform.js',
      {}
    ]
  ],
  # ...
}

这个配置与 jest 的用户配置略有不同,是 jest 内部使用的配置,结构参考 ProjectConfig。与方法 1 类似的,只要在 css 配置中添加对 .less 文件的匹配、在兜底配置中去掉对 .less 文件的匹配就可以了

ProjectConfig:https://github.com/facebook/jest/blob/v24.9.0/packages/jest-types/src/Config.ts#L367-L421

回到 wuzzle.config.js,先新建个对象把原本直接导出的方法放进 modify 字段导出:

module.exports = {
  modify(webpackConfig, webpack, wuzzleContext) {
    // Place the directly exported top-level function here.
  },
};


之后,继续在 wuzzle.config.js 添加 jest 字段来修改 jest 编译配置:

module.exports = {
  modify(webpackConfig, webpack, wuzzleContext) { // ...
  },

+  jest(jestConfig, jestInfo, wuzzleContext) {
+    for (const transformItem of jestConfig.transform) {
+      const fileRegExp = new RegExp(transformItem[0]);
+      if (fileRegExp.test('index.css')) {
+        transformItem[0] = '^.+\\.(css|less)$';
+      }
+      if (fileRegExp.test('index.fallback')) {
+        transformItem[0] = '^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|less|json)$)';
+      }
+    }
+  },
};

现在,运行 test 脚本就可以同样看到 CRA test 中引入 less 的效果了。

不一样的是,因为关闭了 webpack 编译,性能可能会好一些。


更进一步定制


截止这里,已经定制了 CRA 应用的所有脚本,就不再需要 eject 脚本了,可以编辑 package.json

  "scripts": {
-    "eject""react-scripts eject",
  }


在真实项目中,也许还会用到 SSR(服务端渲染)、E2E(端到端测试)、深入配置 eslint。关于如何结合使用 CRA 和 wuzzle 更进一步搭建真实应用,可以

参考官方示例:https://github.com/host1-tech/wuzzle/tree/v0.6.3/e2e/realworld-use/fixtures/react-scripts

写在最后



目前,文章中的示例工程已经收录在:https://github.com/host1-tech/wuzzle-blog/tree/main/001/demo,读者朋友可以按需打开参考,有任何疑问或想法,欢迎留言。此外,如果对 wuzzle 有任何疑问或想法,欢迎在 https://github.com/host1-tech/wuzzle/issues新建 issue,中英文都可以。如果有兴趣和时间贡献代码,欢迎提交 PR,具体可以参考开发引导:https://github.com/host1-tech/wuzzle/blob/master/CONTRIBUTING.md。最后,如果觉得小工具有帮助,可以在 GitHub repo wuzzle:https://github.com/host1-tech/wuzzle 点个小 ⭐️,比心。




SegmentFault 思否社区小编说


自 2022-07-01 起 SegmentFault 思否公众号改版啦!之后将陆续推出新的栏目和大家见面!(请拭目以待呀~


在「社区精选」栏目中,我们将为广大开发者推荐来自 SegmentFault 思否开发者社区的优质技术文章,这些文章全部出自社区中充满智慧的技术创作者哦!


希望通过这一栏目,大家可以共同学习技术干货,GET 新技能和各种花式技术小 Tips。


欢迎越来越多的开发者加入创作者的行列,我们将持续甄选出社区中优质的内容推介给更多人,让闪闪发光的技术创作者们走到聚光灯下,被更多人认识。


「社区精选」投稿邮箱:pr@segmentfault.com

投稿请附上社区文章地址




点击左下角阅读原文,到 SegmentFault 思否社区 和文章作者展开更多互动和交流,“公众号后台回复“ 入群 ”即可加入我们的技术交流群,收获更多的技术文章~

- END -


关注公众号:拾黑(shiheibook)了解更多

赞助链接:

关注数据与安全,洞悉企业级服务市场:https://www.ijiandao.com/
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接