The introduction

  • Front-end infrastructure engineering “rampant”, focus on business code can not improve, do you feel empty feel lonely feel cold?

  • A lot of people want to write their own libraries, but they don’t have the means, they have the skills, they don’t get to use them, they get stuck building them.

  • Every time you build, you find that popular libraries use advanced build techniques, and you still use vue-cli-service generic package of vue-CLI. Doesn’t it seem too high? (VuE-CLI encapsulates all types of resource packaging rules, one-click packaging is actually very convenient, general libraries and components do not need to customize, especially now vite2.0 built-in packaging configuration is also enough out of the box, very convenient)

This article is designed to share how to create monorepo/ Packages project management method, generate DTS declaration file based on rollup package TS code, with custom script one-click support for packaging, minimize changes (rename) can be moved to used. Basic packaging and code introduction are already configured. If there are no special requirements, it can be used after the name change (it may write scaffolding, ts-Monorepo-starter or something later).

Transfer statement

This article is for the purpose of sharing, there is no benefit behavior. You can use the content of this article without informing me, including reprinting, copying and modifying any content of this article.

yarn workspace

Yarn Workspace is a new code organization that allows you to handle all packages dependencies and develop projects easily using a single line of code yarn Install

As early as yarn 1.0, workspaces were supported by various warehouses, such as react, Babel, vue3.0, vite2, etc., which proved the excellence of yarn workspaces in practice.

Benefits of using Yarn Workspace

Of course, one-click YARN Install is only superficially beneficial; The inherent benefits are as follows:

  • All of your libraries will be destroyed by each otherSymbolLink togetherFor example, vite2.0 needed to use YARN Link to create a global Vite Symbollink in vite rootdir separately before reconstructing workspace, and then in playgroundyarn link vite, can be usedvite binaryAnywhere, after using workspace, you can create one directly under Packagesprivate: trueYarn Install can create SymbolLink directly from vite. Furthermore, each package is linked to each other.
  • Dependencies for each package are installed in the node_modules directory of rootdir, which allows YARN to optimize packages automatically to handle version and dependency issues.
  • Versions can be controlled using a lockfile, common libraries canyarn add -W ** Install workspace public area, private libraries canyarn workspace project-1 add package -DInstall dependent packages into packages/project-1 projects. So public libraries can keep the same version, private libraries can install each other, perfect.

I remember when I first started debugging Vuetify, I didn’t know about workspace. I didn’t know about Lerna, I didn’t know about dependencies in Doc, I didn’t know about lerna. This structure is easy to find code, dependency clear, clear reference between libraries, incense.

Create a Workspace quickly

packages
|--project1
|  |--package.json
|--project2
|  |--package.json
package.json
Copy the code

Create a directory and use YARN init -y to create it quickly.

  • inrootdirSet in package.jsonprivate: trueAnd set up your package directory

Since package.json is only used to manage dependencies, other information is useless and can be deleted at will.

{
+ "private": true,
- "name": "rootdir",
  "main": "index.js",
+ "workspaces": [
+ "packages/*"
+]
}
Copy the code

Worksapces/Packages will house the libraries we will be developing, such as project1 and Project2 above

As simple as that, dependencies between packages are created when you type YARN or YARN Install at the command line. All you have to do is press

Yarn workspace

and yarn add -w so that libraries or global public dependencies can be installed.

More workspace related command lines will not be covered here, even if you use Lerna.

rollup

The most popular packaging tools are still WebPack and rollup. Webpack is one of the most popular packaging tools in the world. I use rollup because the configuration of rollup is very simple, and rollup is always smaller than WebPack when packaged in Gzip. Another reason is that playground here uses vite newly developed by Yu Yu Creek for debugging service, so it uses the same packaging tool as Vite (vite has used esbuild code after reconstruction, uses rollup-like API, and is compatible with some rollup plug-ins. Fuck me, this is to rob the webpack bowl, and also to rob the rollup chopstick).

The purpose of the configuration

  • Support all libraries to be packaged in one click for their respective platforms (UMD, ESM, IIFF, CJS…)
  • Support for a single library to listen for changes and compile files
  • Supports the generation of DTS files and TS Type Doc
  • Support package script painless transfer, the future only need to copy the code can be used

Eslint, Prettier code formatting, unit testing, TS type check, extranet command lines, etc.

What follows is a step-by-step guide to writing packaging and debugging scripts as needed.

The structure of step-1 library

Workspaces: [” Packages /*”] workspaces: [” Packages /*”] Workspaces: [” Packages /*”] Workspaces: [” Packages /*”]

Json file name is the same as the bundle name in packages/*/package.json, so that the script can enter the correct bundle file when packaging. It is also recommended to use NPM scope to prevent package conflicts and rename. 2 minutes for code, 2 hours for naming (package.json uses @scope/lib-name for name, e.g., @rollup/rollup-typescript)

In order to support full import, esM tree-shaking, SRC has an index.js/ts file that imports and exports all, and is used as a packaged entry file.

Step-2 universal rollup.config.js

Rollup and Webpack are the same, ensuring input, determining output, and the intermediate plugin handling code and resources.

To get the input

First of all, our libraries are stored in the packages directory. We need to pass in the packaged TARGET, which is handled by the environment variable TARGET.

import path from 'path'

const pkgsDir = path.resolve(__dirname, 'packages')
const pkgDir = path.resolve(pkgsDir, process.env.TARGET) // TARGET we will provide in the script

Copy the code

Since our rollup is started by a script, like nodejs rollup.rollup(), we need to pass the full file path to rollup without knowing bsaeUrl, and also support deep file structure:

// Create a new resolve instead of the path native resolve, which already handled pkgDir
const resolve = (filename) = > path.resolve(pkgDir, filename)
Copy the code

To deal with the output

SRC /index.ts SRC /index.ts SRC /index.ts SRC /index.ts SRC /index.ts SRC /index.ts We can use buildOptions in package.json to write the differences. This allows you to package each library specifically by overwriting arguments:

// For the build, we used the CLI inline > pkgOptions > defaults priority to handle parameter overrides

const pkgOptions = pkg.buildOptions || {}
const defaultFormats = ['esm'.'umd']
// cli inline specifies the command that you want to manually control the packaging behavior when you enter a command on the command line. The command can be the packaging format, listener mode, or dev environment, etc
const inlineFormats = process.env.FORMATS && process.env.FORMATS.split(', ')
const packageFormats = inlineFormats || pkgOptions.formats || defaultFormats

Copy the code

None of the package formats will correspond to an input/output, so we’ll generate an array of the complete configuration parameters according to format and export it to rollup for use:

const outputConfigs = {
  esm: {
    file: resolve(`dist/${name}.esm.js`),
    format: 'es'
  },
  global: {
    file: resolve(`dist/${name}.global.js`),
    name: camelCase(name),
    format: 'iife'
  }
  / /... CJS UMD and so on
}

const packageConfigs = packageFormats.map(createConfigWithFormat)

function createConfigWithFormat(format) { return createConfig(format, outputConfigs[format])}

export default packageConfigs
Copy the code

For UMD, iIFE, rollup packaging is set up so that the execution function needs to provide the name attribute to mount on global. In addition, the production environment needs to change the file name of the output, and compress the code, etc. The difference is obvious and the code is simple, so I won’t show it here. So, processing input and output is OK:

//rollup.config.js needs to export a configuration or configuration array by default. This configuration must be one input for one output
Since plugins are generic, we create a method to handle each different format

// Format is the package format, output is the output configuration, and plugins are special needs plug-ins
function createConfig(format, output, plugins = []) {
    // Since output is not the same in dev and production environments, we will leave the differences outside
    // Make createConfig always focus on the default behavior of the generic configuration
    // Also, different scenarios may have some special plug-ins to use, so in addition to the same plug-ins, these special plug-ins are passed in
    
    const entryFile = pkgOptions.entry || 'src/index.ts'
    return {
        input: resolve(entryFile),
        output,
        plugins: [
            common-plugin1(),
            common-plugin2(),
            ...plugins
        ]
    }
}

Copy the code

Step step – 1-2

  • We need to know that the package TARGET (” project-1 “) comes from the environment variable TARGET
  • We need to know the packaging format (” ESM, CJS, UMD…” ) from cli inline, buildOptions, defaults
  • We need to know that the package environment dev or productin comes from env.node_env, or more about other environment variables

Step-3 starts writing the packaging script

Dev package

  • The CLI has the highest inline priority and can override all parameters. Therefore, we need to obtain the parameters in the command line first, which is recommended hereminimistLibrary that automatically parses and formats parameters

Suppose we debug the shared library, package it in ESM format, and command behavior:

yarn dev shared -f esm
# or
yarn dev shared --formats esm
# or
yarn dev --formats esm shared
Copy the code

Output results:

// Named arguments use --word or -w (-- word or -word abbreviation, space, value)
// Anonymous parameters are pushed into args._
const args = require('minimist')(process.argv.slice(2))
console.log(args) // target { _: [ 'shared' ], f: 'esm' }
Copy the code
  • Now that we can customize the command line as we wish, we can customize the command line parameters of rollup:
// -w means to listen for file changes, -c means to use config file, // --enviroment can pass environment variables such as NODE_ENV. Rollup-wc --enviroment [environment variable]Copy the code

To be able to execute the command line, we also need a simplified command line library execa

const execa = require('execa')

// If you are focusing on a library, you can also specify parameters by default, or package objects in package.json scripts
FuzzyMatchTarget is used to check whether the current target exists in the Packages directory
const target = args._.length ? fuzzyMatchTarget(args._)[0] : 'split-layout'
const formats = args.formats || args.f
const sourceMap = args.sourcemap || args.s

execa(
  'rollup'['-wc'.'--environment'[`TARGET:${target}`.// Pass the package object
      `FORMATS:${formats || 'global'}`.// Pass the package format

      // Sourcemap is the index of the source code after the code is packaged, so that the browser can find the error code location
      sourceMap ? 'SOURCE_MAP:true' : ' ' 
    ].filter(Boolean).join(', ')] and {stdio: 'inherit'})Copy the code

Production of packaging

  • Production environment packaging principle is the same as above, in the main process differences are:
  1. Output the DTS file
  2. Package multiple libraries at the same time
  3. Generate the parameter type document
  4. Gizp file
  5. Remove the development environment prompt
  6. Verify the output file and check the file size
Output DTS files and type documents
  • rollup-plugin-typescript2Package the TS code and output the DTS file

Use @rollup/plugin-typescirpt or rollup-plugin-typest2

Rollup /plugin-typescirpt is the official plugin, rollup-plugin-typescript is based on the syntax and semantics of the official plugin, so I recommend the latter so that problems can be found early in development.

In this case we need to forget about adding typescript plugins to the rollup configuration

Typescript plugins have default configuration, but our libraries may have different goals. For example, browsers compile to ES5, plugins compile to ES6, and so on. We create a tsconfig.json file in this library to override the configuration we need. Override and value are also supported. Override and value are configured in tsconfig.

+ import typescript from 'rollup-plugin-typescript2'

  function createConfig(format, output, plugins = []) {
      return {
          plugins: [
+ typescript({
+ tsconfig: resovle('tsconfig.json')
+}).. plugins ] } }Copy the code
// packages/shared/tsconfig.json
{
  "extends": ".. /.. /tsconfig.json"."compilerOptions": {
    "baseUrl": ".".// respecify the reference path
    "declaration": true.// Whether to output the declaration file
    "declarationMap": true."outDir": "dist"}}Copy the code
  • @microsoft/api-extractorPut the DTS files together and generate the Type Doc

The default configuration file is api-extractor.json. Since it is generic, it will not be shown here and can be directly viewed in the repository

We are using a script file, we need to manually call the api-Extractor plug-in:

const { Extractor, ExtractorConfig } = require('@microsoft/api-extractor')

const extractorConfigPath = path.resolve(pkgDir, 'api-extractor.json')
const extractorConfig = ExtractorConfig.loadFileAndPrepare(
  extractorConfigPath
)
// invoke means to use custom prepared files, and the scenario is to pass in the configuration manually for script execution
const extractorResult = Extractor.invoke(extractorConfig, {
  localBuild: true.showVerboseMessages: true // Output more detailed information
})
Copy the code

Execa is an asynchronous execution function. We need to wait for all TS files to be packaged and output corresponding DTS files before integrating DTS files together using api-Extractor. If there is local types folder, There is also a code in the build script that appends the DTS file of types to the DTS file of Dist. See the scripts/build.js file in the repository for details

At this point, the important step in packaging is complete, and other plug-ins can be added as needed.

Gzip and output file size information

Gzip is simpler, using:

const { gzipSync } = require('zlib')

// fs reads the file directly and gzip is done
const file = fs.readFileSync(filePath)
const minSize = (file.length / 1024).toFixed(2) + 'kb'
const gzipped = gzipSync(file)
const gzippedSize = (gzipped.length / 1024).toFixed(2) + 'kb'
Copy the code

To summarize the operation

  • A library can be debugged individually or packaged with a single click from a simple command line in the root directory
yarn dev project-1 -f esm

yarn build -t
Copy the code

Json, scripts /dev/js, scripts/build.js, api-extractor.json, rollup.config.js

The above is just a common package /projects directory format. You can customize the packaging script to your own needs, so long as the input, output, target, and formats are clear, there is no problem

  • All instance code is under the repository compose/ Initial, which optimizes bundle code and does not write business code
  • In the future, the repository will write a drag and drop library to support T font or field font layout based on native js according to vscode requirements

The reference code for this article comes from VUE-Next. After reading this article, you should be able to master the packaging and build process of vue-Next projects

Github storage address

Graduation work for two years, began to step by step precipitation of their own, the new organization and github account, no longer muck about, everything began to start again, behind the beginning of the article, less touch fish, welcome to leave a message to discuss and point out mistakes, welcome at the same time big brother on-site teaching.