I took over a wechat small program project of the company before, and after being online for a period of time, I need to release more small programs based on this project. The content of these small programs is basically the same, but they have different names, themes, ICONS, and so on; Or, some small programs need to add some custom pages, functions and so on. This article focuses on the evolution of my practice from hand-copying projects to using command line tools to copy projects. This simple command-line tool is crudely called QuickCopy, and the documentation is here.

I developed the applet using Taro 2.2.13, so this tool is currently being developed in this environment as well. In addition to the Taro plugin, 2.x can use this tool.

Before developing tools

defineConstants

In the beginning, there weren’t many projects, so I just did it by hand. For example, we already have A small program called applet A. Now we need to copy A new applet from this applet and package it to implement the following three simple requirements:

  1. Set config. Window. NavigationBarTitleText in the navigation bar shows the respective small program name;

  2. Set config. The tabBar. SelectedColor in tabBar selected according to different color;

  3. Customize the config.tabBar icon for the new applet, and applet A continues to use the original icon.

First, we use global constants to modify config in app.jsx:

// app.jsx
config = {
  tabBar: {
    SelectedColor: '#000'
    selectedColor: __MAIN_COLOR,
    list: [{// Before transformation: 'assets/ ICONS /tabbar-home-s.png'
        selectedIconPath: 'assets/' + __ICON_DIR + '/tabbar-home-s.png'}},window: {
    // 改造前: navigaionBarTitleText: '小程序A'
    navigationBarTitleText: __APP_NAME
  }
}
Copy the code

Then create Taro compiled configuration files build-configa.js and build-configb.js for the two applet programs respectively in the config directory and write defineConstants:

// build-configA.js
module.exports = {
  defineConstants: {
    __APP_NAME: JSON.stringify('Small program A'),
    __MAIN_COLOR: JSON.stringify('# 000'),
    __ICON_DIR: JSON.stringify('icons')}}// build-configB.js
module.exports = {
  defineConstants: {
    __APP_NAME: JSON.stringify('Small program B'),
    __MAIN_COLOR: JSON.stringify('# 111'),
    __ICON_DIR: JSON.stringify('icons-b')}}Copy the code

Finally, to compile the package applet A, I need to merge build-configa.js with the base build configuration at the end of config/index.js. The same is true when compiling the package applet B.

module.exports = function(merge) {
  return merge({}, config, require('./dev'), require('./build-configA.js'))}Copy the code

Running these two small programs, we can see that they display their own name and theme color, and small program B also displays a customized tabBar icon.

sass.resource

Since we’ve defined a theme color __MAIN_COLOR in the global constant above, we definitely need to write different theme styles for different applets as well. First, create theme style files for each of the two small programs under the SRC /style/themes directory. Global injection is performed in build-configa. js and build-configb.js:

// build-configA.js
sass: {
  resource: [
    'src/style/themes/themeA.scss'./ / build - configB. Js in SRC/style/themes/themeB SCSS
    'src/style/variable.scss'.'src/style/mixins.scss']}Copy the code

Global injection eliminates the need to write @import ‘xxx.scss’ again and again in the style file. However, it is important to note that the three files that need to be injected must be fully listed. While style files like variable. SCSS and mixins. SCSS can obviously be shared across all projects, if injected only in config/index.js, Injecting only theme style files into build-configa.js or build-configb.js will not work.

// build-configA.js
sass: {
  resource: ['src/style/themes/themeA.scss']}// config/index.js
sass: {
  resource: [
    'src/style/variable.scss'.'src/style/mixins.scss'].projectDirectory: path.resolve(__dirname, '.. ')}// Merge is the result of the two configurations
sass: {
  resource: [
    'src/style/themes/themeA.scss'.'src/style/mixins.scss'].projectDirectory: path.resolve(__dirname, '.. ')}Copy the code

That is, for arrays, merge by index position.

So far, we have implemented different name, icon, and theme styles for different applets. But in the spirit of being lazy when you can, I think these steps are already a bit cumbersome. Imagine if a new project needs to be released and we need to do these things manually:

  1. Create the compiled configuration file config-project.js for the project in the config directory.

  2. If necessary, create custom ICONS directories for new projects;

  3. Create a theme style file for the new project and inject it globally in config-project.js;

  4. Write defineConstants in config-project.js, and write constants that differ from project to project, and other constants in config/index.js;

  5. Merge the compiled configuration of the new project in config/index.js;

  6. Currently all projects share the project.config.json file in the root directory, so you need to change the appID before compiling.

If any of these projects ever need to be updated, imagine:

  1. First, modify the project configuration path of the merge in config/index.js.

  2. Then modify the appID in project.config.json;

  3. Finally compile and package;

  4. And so on;

So, can these steps be handed over to a program? To be as lazy as possible, I wrote a simple command line tool. It can do the following things for us:

  1. Using config/index.js as the template, extract part of the compiled configuration, create and write the Taro compiled configuration file to the new project;

  2. Using the root directory project.config.json as the template, create a small program project configuration file of the new project.

  3. Create a theme style file for the new project and inject it globally at compile configuration;

  4. Look for custom ICONS for new items when you package them, and if so, replace them.

After developing the tool

Suppose we have A compile configuration for an existing project applets A:

// config/index.js
const config = {
  projectName: 'projectA'.outputRoot: 'dist'.copy: {
    patterns: [{from: 'src/components/wxs'.to: 'dist/components/wxs' },
      // ...]},sass: {
    resource: [
      'src/style/variable.scss'.'src/style/mixins.scss'.// ...].projectDirectory: path.resolve(__dirname, '.. ')},defineConstants: {
    HOST: JSON.stringify('www.baidu.com'),
    APP_NAME: JSON.stringify('Small program A'),
    MAIN_COLOR: JSON.stringify('# 999'),
    // ...}}Copy the code

In this compilation configuration, the output directory of the project is specified as dist, variable. SCSS and mixins. SCSS files are globally injected, and three constants are specified. Since Taro does not package WXS, WXS are manually copied to the output directory in Copy. Patterns.

Before copying the project, let’s make a little change to the compile configuration. In defineConstants, we find constants that differ between items, in this case APP_NAME and MAIN_COLOR, start with the double underscore __ so the tool knows that these constants are different and the rest are the same across all items. SCSS then finds the theme-related variables that need to be written to the project’s respective theme style files.

For the existing project projectA, it is best to do a copy as well. This allows it to have a separate compiled configuration, and config/index.js is shared not only as a base compiled configuration for all projects, but also as a template for creating a separate compiled configuration for new projects.

Copy the project

Taking separating an existing projectA project as an example (copying a new project is similar), do it in the root directory:

qc copy projectA wx123456789a
Copy the code

Tools can do this for us:

  1. Create the Taro compiled configuration file at config/ config-projecta /index.js;

  2. In the root directory project. Config. Json as templates to create WeChat small application project configuration file, the path to the config/config – prjectA/project. Config. Json;

    {
      "miniprogramRoot": "dist-projectA/"."projectname": "projectA"."appid": "wx123456789a"
    }
    Copy the code

    The rest of the content will be the same as project.config.json in the root directory;

  3. Look for style directories in the order SRC /style, SRC /styles, and SRC/CSS. If yes, create a themes/ projecta. SCSS theme style file in the corresponding directory. If none of the preceding directories exist, they are created in SRC /style by default. Specific styles need to be written manually;

  4. From config/index.js find the style file for global injection, sass.resource, and inject it into config/ config-projecta /index.js along with the theme style file created in the previous step:

    sass: {
      resource: [
        'src/style/themes/projectA.scss'.'src/style/variable.scss'.'src/style/mixins.scss']},Copy the code

    The theme style file comes first so that variable. SCSS and mixins.scss can rely on the theme style.

  5. Find the file from config/index.js that needs to be copied to the output directory, that is, copy.patterns, and change the path specified by to;

    copy: {
      patterns: [{from: 'src/components/wxs'.to: 'dist-projectA/components/wxs'}}]Copy the code
  6. Find constants from config/index.js that differ from project to project, i.e. constants starting with __ in defineConstants, and automatically add a new constant named __PROJECT;

    defineConstants: {
      __APP_NAME: JSON.stringify('Small program A'),
      __MAIN_COLOR: JSON.stringify('# 999'),
      __PROJECT: JSON.stringify('projectA')}Copy the code

So the final config/ config-projecta /index.js would look something like this:

module.exports = {
  projectName: 'projectA'.outputRoot: 'dist-projectA'.defineConstants: {
    __APP_NAME: JSON.stringify('Small program A'),
    __MAIN_COLOR: JSON.stringify('# 999'),
    __PROJECT: JSON.stringify('prjectA')},copy: {
    patterns: [{from: 'src/components/wxs'.to: 'dist-projectA/components/wxs'}},sass: {
    resource: [
      'src/style/themes/projectA.scss'.'src/style/variable.scss'.'src/style/mixins.scss']}}Copy the code

As for the icon issue above, since Taro provides plugin capabilities, we no longer need to introduce the __ICON_DIR constant and modify the selectedIconPath as we did above. Simply add QuickCopy /plugin-copy-assets to plugins in config/index.js.

For example, we originally put the icon in the SRC/assets/ICONS directory, if we want to specify custom for projectA tabBar. List. SelectedIconPath, Simply create a new directory named SRC/Assets/ICONS -projectA and store projectA’s customized ICONS in this directory.

When packaging projectA, the plugin will go to Assets/ICONS -projectA to see if there is a custom icon, if so, use this icon, and if not, use the default icon from Assets/ICONS.

The same goes for other ICONS.

Preparation before compilation

When we need to compile projectA, we do this in the root directory:

qc prep projectA
Copy the code

The tool does two things:

  1. Create the config/build.export.js file and export config/ config-projecta /index.js;

    const buildConfig = require('./config-projectA/index')
    module.exports = buildConfig
    Copy the code
  2. The config/config – projectA/project. Config. Json is copied to the root directory.

We simply merge build.export.js at the end of config/index.js, followed by the Taro compile instruction in the root directory.

How do I add custom pages

Perhaps one day we will receive A request to add A custom page for applet A. We added this page path to app.jsx config, but we didn’t want it to be packaged with other applets.

The initial approach I used was simple: comment out the page path while packing other applets, and open the comment when packing applets A.

We can use babel-plugin-preval (also mentioned in the Taro document) and the __PROJECT constant writing logic above to determine which projects need to package custom pages and which do not.

First, extract config.pages as a separate file, such as:

// pages.js
module.exports = function(project) {
  const pages = [
    'pages/tabBar/home/index'.'pages/tabBar/profile/index'
  ]
  if (project == 'projectA') {
    pages.push('pages/special/index')}return pages
}
Copy the code

Then modify app.jsx:

const project = __PROJECT
class App extends Component {
  config = {
    // Use project instead of passing __PROJECT directly because I found an error when compiling directly with __PROJECT when testing
    pages: preval.require('./pages.js', project)
  }
}
Copy the code

This way we can add custom pages only by modifying pages.js, not only avoiding being packaged with unwanted projects, but also making it clear which projects have custom pages and which don’t. The same can be done for subpackages and tabbar.list.

The last

So far, this tool has been developed according to the business needs of the company, and there are not many main features, there are still quite a few limitations. I’m also exploring ways to make it easier to package customized pages written for different projects, so the tool will continue to be updated.