background

Recently, two CMS (content management) systems have been built. In order to reduce the cost of development switching project and reduce the cost of users, the two systems use a unified scaffolding ANTD-Pro.

In the process of function development, it is found that there are many same functions and logic between the two systems. Maybe I’ll write it for project A, and then I’ll write it again for project B that needs the same implementation.

For example, login page UI, personalized Table component encapsulation, PageLoading component, request encapsulation, etc., are some TS/TSX files, some components may rely on less, JPEG/PNG/SVG and other resources.

Needless to say, this is definitely not reasonable, how to avoid copy/paste? The first thought was to pull out the public module and publish an NPM package.

Common packaging schemes

When it comes to modules, the first thing that comes to mind is how to package. There are several common packaging tools:

tool advantages disadvantages
webpack/rollup There is nothing that cannot be achieved 1. Troublesome configuration (multiple entries/outputs and component paths should be kept)
typescript + babel The logic is clear, TSC generates D. ts + Babel copy-files can easily generate components less, D. ts, JS files to the specified directory Can only handle JS, not less, static resources
Zero-configuration package tools Such as Parcel and Microbundle Zero configuration Fixed scenarios, few configurable items, and high customization costs

As a simple comparison, webPack /rollup is basically the only choice for customizing packaged components and packaging less/images.

Package as commonJS specification, compile less to CSS, and generate d.ts file.

The package directory tree looks something like this:

@scope/common
  - package.json
  - dist/
    - assets/
        - logo.jpg
    - components/
        - MyComponent/
            - index.css
            - index.js
            - index.d.ts
    
Copy the code

Components will then be used in projects in the same way as Antd components alone. Manually import JS components and then CSS files

import MyComponent1 from '@scope/common/dist/components/MyComponent1'
import '@scope/common/dist/components/MyComponent/index.css'
import MyComponent2 from '@scope/common/dist/components/MyComponent2'
import '@scope/common/dist/components/MyComponent2/index.css'
Copy the code

Routine operation, the scheme is certainly feasible, but but compared with the way before pulling out the module, it is really very useful! No! Party!!!! Then! The original usage, which can be imported directly into a component, automatically wraps up other dependencies within the component, which is much simpler.

import MyComponent1 from '.. /.. /components/MyComponent1'
import MyComponent2 from '.. /.. /components/MyComponent2'
Copy the code

And there is another problem, what about the image resources used in the original component? Is it all base64? This will greatly increase the volume of the package.

Do I have to pack it?

Going back to the original requirements, the purpose of pulling out the common modules was to allow components and logic to be reused without the same code being scattered all over the place. This part of the code is only the common code extracted from the two management systems, and does not have global universality.

It is best to use it in the same way as before, with TSX? The file is the entry, and other internal dependencies can be handled by the correct loader.

Make a bold assumption: release typescript, less code directly, and let the actual users decide how to package it.

Of course you can! For the packaging tool Webpack, it doesn’t matter which directory the files need to be packed in. The test, include, and exclude parameters in module.rule specify which loaders are used for which files.

For example, webpack makes my-project code that is not node_modules pass through babel-loader

module: {
    rules: [
      {
        test: /\.(js|mjs|jsx|ts|tsx)$/,
        include: [
          /my-project/
        ],
        exclude: [
          /node_modules/
        ],
        use: [
          ... babel-loader
        ]
      }
   ]
}
Copy the code

Umi (antD-Pro) uses webpack as a packaging tool. [email protected] and above automatically process Typescript in node_modules

webpackConfig.module

    .rule('ts-in-node_modules')

      .test(/\.(jsx|ts|tsx)$/)

      .include.add(/node_modules/).end()

      .use('babel-loader')

        .loader(require.resolve('babel-loader'))

        .options(babelOpts);
Copy the code

The syntax used here is Webpack-chian, which generates the WebPack configuration by chaining it.

face

This is the end of the original article. When I was happy to develop it, I got an error when I pulled out the public package and used NPM link to manage the project.

TSX does not go through the babel-loader correctly. After carefully validating the generated WebPack configuration items, we focused on the Webpack itself. Could it be the soft chain?

Consult the documentation for the webpack soft link configuration item resolve.symlinks

Whether to resolve symlinks to their symlinked location.

When enabled, symlinked resources are resolved to their real path, not their symlinked location. Note that this may cause module resolution to fail when using tools that symlink packages (like npm link).

By default, WebPack resolves the soft chain to a real path (turning this configuration off can cause other problems). So the problem is that when using NPM link, the regular expression /node_modules/ test does not match this path, resulting in the code for the final public module not being processed by babel-loader.

This error does not occur when NPM publishes and then installs.

Now that the problem is clear, all that remains is to modify the WebPack configuration.

How does UMI modify the WebPack configuration?

Umi uses the chainWebpack item to modify the configuration, using the Webpack-chain syntax. All you need to do here is add the absolute path to the public module.

// config/config.ts


chainWebpack(memo) {
  memo.module
    .rule('ts-in-node_modules')
    .include.add(require('path').join(__dirname, '.. /.. /packages/'));
  return memo;
}
Copy the code

Done, public modules can now be removed painlessly ~ Happy Coding

To facilitate the use of components, you can define index. TSX for reexport rather than digging into the component directory.

// index.tsx reexport
export { MyComponent } from './src/components/MyComponent'

// new usage
import { MyComponent } from '@scope/common'

vs

// old way
import MyComponent from '@scope/common/src/components/MyComponent'
Copy the code

You also need to modify package.json to declare main and types

{
    main: './index.tsx'.types: './index.tsx'
}
Copy the code

Last question, Monorepo, is it ok to put the package directly in the public directory without sending it?

In the project, we use LerNA to manage multiple front-end projects. All the code is in the same folder. Is it ok to reference the code in the public folder instead of sending packages?

-packages / -module-a / -module-b / -commons // / import PageLoading from '.. /.. /.. /commons/PageLoading'Copy the code

The problem with this form is that the module-A code goes beyond what it should be responsible for. And there is no NPM version of the concept, it depends on the whole, prone to [uncertain] updates, there are branch dependency issues.