Use the incremental build tool Preset to build your own template library

jump__jump
中文

How did you start a project? Is it based on the scaffolding provided by the current technology stack or from npm init ?

I didn't have to choose before. I had to face search engines. Based on webpack or rollup to build the project step by step, many errors may occur during the development process. But now I just want to focus on the current business and quickly build my own project after choosing the right scaffolding. In this way, I can delegate a lot of maintenance work to open source authors.

Of course, well-known scaffolding tools (Vue CLI, Umi, Vite, etc.) needless to say, here I recommend a few handy tools.

  • microbundle-crl focuses on the construction of React components
  • tsdx focuses on the construction of the TypeScript library
  • crateApp generates a project package according to the current option configuration (multiple basic construction tools Webpack, Parcel, Snowpack)

But no matter which template library or scaffold it is, it will not fully meet the needs of the current business, and developers need to modify it based on the current template. For example, you need to add an open source agreement to the project, modify the project name, and add different dependencies to the project.

In terms of construction, there are currently two problems:

  • Lots of repetitive operations

If the work frequency of generating the project is high, for example, write a business component a week. Although every time you add an open source agreement, modify the project name, and add specific dependencies in a project, it is a small task, but the frequency is also a troublesome thing.

  • The underlying dependencies cannot be directly upgraded

If the developer modifies the current template, then the scaffold cannot be directly upgraded when there is a destructive update (this problem is of course relatively rare). Although some changes will be recorded during the development process. But as time goes by, developers will not know exactly which files need to be edited or deleted to make the upgraded project work properly.

much to say, let’s take a look at how the tool 160a68e988d82b Preset solves this series of problems.

Use Preset

First create a project, take vite as an example, package.json is as follows

{
  "name": "vite-preset",
  "version": "0.0.1",
  "author": "jump-jump",
  "license": "MIT",
  "preset": "preset.ts",
  "prettier": {
    "printWidth": 80,
    "tabWidth": 2,
    "trailingComma": "all",
    "singleQuote": true,
    "arrowParens": "always",
    "useTabs": false,
    "semi": true
  },
  "devDependencies": {
    "apply": "^0.2.15"
  }
}

Perform the following operations, we will go to the my-vue-app file.

# npm 6.x
npm init @vitejs/app my-vue-app --template vue

After getting the results generated by the current command, we copy the current generated files to the templates (templates/vite) folder in the root directory of vite-preset.

Then we write the Preset command through preset.ts (corresponding to preset": "preset.ts" in package.json).

import {Preset, color} from 'apply'

// 当前编写项目的名称,会在控制台中展示
Preset.setName('jump-jump vite preset')

// 从 templates/vite 中提取所有文件,并携带以 . 开头的文件 如 .gitignore 等    
Preset.extract('vite')
  .withDots()


// 更新当前 package.json 文件,添加依赖 tailwindcss,移除依赖 sass
Preset.editNodePackages()
  .add('tailwindcss', '^2.0')
  .remove('sass')

// 安装所有依赖
Preset.installDependencies()

// 运行提示
Preset.instruct([
  `Run ${color.magenta('yarn dev')} to start development.`,
]).withHeading("What's next?");

finished!

We can try the effect, I look for a suitable folder, and then run the command:

// 解析 vite-preset 项目
npx apply C:\re-search\vite-preset

Preset.png

The previously saved vite template folder is decompressed to the current folder, and the dependency is also replaced at this time. Of course, we can also specify the folder to install, such as

npx apply C:\re-search\vite-preset vite-demo

The vite template is decompressed to the vite-demo folder in the current folder.

Not only can we use the local path, of course, we can also use the github path. Such as:

npx apply [email protected]:useName/projectName.git

// 等同于
npx apply username/projectName

At present, the effect is barely okay. In fact, we can operate far more than the ones shown above, so I will start to interpret the various commands of Preset one by one.

Play with Preset

setName project name setting

As shown in the picture above, the command will be displayed in the console after it is successfully set.

Preset.setName('jump-jump preset')

setTemplateDirectory template directory settings

This operation will modify the extraction root path. If it is not used, the default option is templates.

// 文件提取根路径被改为了 stubs 而不是 templates
Preset.setTemplateDirectory('stubs');

extract folder extraction

This operation allows files to be extracted from the preset template directory to the target directory. In most cases, this command can solve most problems.

// 当前会提取整个根样板 即 templates 或者 stubs
Preset.extract();

// 当前会提取 templates/vite 文件夹到根目录
Preset.extract('vite'); 

// 先提取 templates/presonal,然后提取  templates/presonal 文件夹
Preset.extract('vite'); 
Preset.extract('presonal'); 

// 等同于 Preset.extract('vite')
Preset.extract().from('vite'); 

// 提取到根路径下的 config 文件夹
Preset.extract().to('config');

// 遇到文件已存在的场景 [ask 询问, override 覆盖, skip 跳过]
// 注意:如果询问后拒绝,将会中止当前进度
Preset.extract().whenConflict('ask');

// 在业务中,我们往往这样使用,是否当前式交互模式?
// 是则询问,否则覆盖
Preset.extract().whenConflict(Preset.isInteractive() ? 'ask' : 'override')


// 如果没有此选项,以 .开头的文件(如 .gitignore .vscode) 文件将被忽略。
// 注意:建议在样板中使用 .dotfile 结尾。 
// 如: gitignore.dotfile => .gitignore
Preset.extract().withDots();

editJson edit JSON file

Use editJson to overwrite and delete the content in the JSON file.

// 编辑 package.json 深度拷贝数据
Preset.editJson('package.json')
  .merge({
    devDependencies: {
      tailwindcss: '^2.0'
    }
  });

// 编辑 package.json 删除 开发依赖中的 bootstrap 和 sass-loader
Preset.editJson('package.json')
  .delete([
    'devDependencies.bootstrap',
    'devDependencies.sass-loader'
  ]);

Of course, Preset provides a simple control item editNodePackages for the node project.

Preset.editNodePackages()
  // 会删除 bootstrap 
  // 无论是 dependencies, devDependencies and peerDependencies
  .remove('bootstrap')
  // 添加 dependencies  
  .add('xxx', '^2.3.0')
  // 添加 devDependencies
  .addDev('xxx', '^2.3.0')
  // 添加 peerDependencies
  .addPeer('xxx', '^2.3.0')
  // 设置键值对         
  .set('license', 'MIT')
  .set('author.name', 'jump-jump')

installDependencies install dependencies

While building the project, we need to install dependencies, which is done here through installDependencies.

// 安装依赖,默认为 node,也支持 PHP
Preset.installDependencies();

// 询问用户是否安装
Preset.installDependencies('php')
  .ifUserApproves();

instruct boot

This command can add a slogan to guide the user step by step to the next step, and can also add a variety of colors.

import { Preset, color } from `apply`;

Preset.instruct([
  `Run ${color.magenta('yarn dev')} to start development.`,
]).withHeading("What's next?");

options set configuration

Developers want to add multiple templates, do they need to develop multiple projects? The answer is no, we can get the parameters through options.

npx apply C:\re-search\vite-preset vite-demo --useEsbuild

The current data will be set to Preset.options.

// 默认设置 useEsbuild 为 true
Preset.option('useEsbuild', true);
// 默认设置 use 为字符串 esbuild
Preset.option('use', 'esbuild');

// 如果配置项 useEsbuild 为 ture 解压 templates/esbuild
// 也有 ifNotOption 取反
Preset.extract('esbuld').ifOption('useEsbuild');

// use 严格相等于 esbuild 解压 templates/esbuild
Preset.extract('esbuld').ifOptionEquals('use','esbuild');

Preset.extract((preset) => {
  // 如果配置项 useEsbuild 为 ture 解压 templates/esbuild
  if (preset.options.useEsbuild) {
    return 'esbuild';
  }

  return 'vite';
});

We can add configuration items when executing npx, as shown below

SignValues
--auth{ auth: true }
--no-auth{ auth: false }
--mode auth{ mode: 'auth' }

input confirm interactive settings

Preset settings are great. But as far as user experience is concerned, it is better to set it through interaction. This way we don't need to memorize each configuration item. Enter data through human-computer interaction, and the current data will be added to Preset.prompt.

// 第一个参数将传入 Preset.prompt
Preset.input('projectName', 'What is your project name?');

// 第三个是可选的上下文字符串,用于定义提示的默认值。
// 如果预设是在非交互模式下启动的,它将被使用。
Preset.input('projectName', 'What is your project name?', 'jump project');

// 编辑脚本
Preset.editNodePackages()
    .set('name', Preset.prompt.projectName)
    .set('license', 'MIT')
    .set('author.name', 'jump-jump')

// 第一个参数将传入 Preset.prompt
// 第三个是可选的上下文布尔值,用于定义提示的默认值。
// 如果预设是在非交互模式下启动的,它将被使用。
Preset.confirm('useEsLint', 'Install ESLint?', true);

delete edit modify file

Delete the files in the generated folder and use delete directly

Preset.delete('resources/sass');

Edit file

// 替换文本字符串
Preset.edit('config/app.php').update((content) => {
    return content.replace('en_US', 'fr_FR');
});

// 替换 README.md 文件中的字符串 {{ projectName }}
// {{prejectName}} => prompts.name ?? 'Preset'
Preset.edit('README.md').replaceVariables(({ prompts }) => ({
    projectName: prompts.name ?? 'Preset',
}));

execute execute bash command

If none of the previous commands satisfy you, you can only execute the bash command! Preset also provides this function, and various parameters can be added in combination with hooks.

// 利用钩子将数据存储到 context 中
Preset.hook(({ context, args, options }) => {
    const allowedOptions = ['auth', 'extra'];

    context.presetName = args[2];
    context.options = Object.keys(options)
        .filter((option) => allowedOptions.includes(option))
        .map((option) => `--${option}`);
});

// 第一个参数是程序或者命令名称,后面是参数,从 context 中读取
Preset.execute('php')
    .withArguments(({ context }) => [
        'artisan', 
        'ui', 
        context.presetName, 
        ...context.options
    ])
    // 修改当前标题,当前执行时会在控制台打印如下字符串,而不是默认字符串
    .withTitle(({ context }) => `Applying ${context.presetName}`);

Think further

Through the study of the Preset library, we can see that Preset has a very good design style and powerful functions. Preset does not build the project from the bottom, but instead helps developers to derive their own tools through some commands, and can also record most of the developers' modifications to the project.

Incremental thinking

In the process of using Preset to build the template, the developer did not modify the original template, which makes it very simple for the developer to upgrade the original sample. The process of building a Preset project is actually modifying the model increment.

We should further use incremental thinking in development, where the opposite of the concept of "increment" is "full amount." Increment will be based on the difference between the current and the past, and only focus on the impact of the difference.

Increment has many practical meanings, we can see:

  • The front end only submits changed data when the front and back ends are interacting
  • rsync incrementally synchronize files
  • Incremental upload of files from the cloud
  • Database incremental backup
  • Incremental code inspection, construction, packaging

Chain call

As the front-end framework brought data-driven, JQuery gradually withdrew from the stage of history (Bootstrap 5 removed JQuery). The continuous upgrade of ES also gives users a lot of help, and users do not need to construct objects by themselves for chain calls. But this does not mean that chained calls are not important.

Because chain calls can record the timing elegantly, developers can rely on the current call for analysis.

Most tools provide different configuration items. At this point, we can directly pass in the configuration items to use the tool.

If the current operation is sequential (the order determines the final result), it is more effective to construct objects for chained calls. Of course you can say that we add an array configuration to determine the order. But in the face of complex sequences, excellent function naming can make it easier for users to understand the code.

Also, if we are faced with complex graph structures, it will be easier to construct objects to select and operate nodes. If there is demand, we even need to generate sql statements based on chained calls.

Encourage

If you think this article is good, I hope you can give me some encouragement and help star on my github blog.

Blog address

Reference

Preset

microbundle-crl

tsdx

crateApp

阅读 1.9k

随笔
希望可以记录下我的成长之旅,也希望可以帮助大家

猎奇者...未来战士...very vegetable

2.5k 声望
4k 粉丝
0 条评论

猎奇者...未来战士...very vegetable

2.5k 声望
4k 粉丝
文章目录
宣传栏