Record front-end XX deployment, how to remove all Chinese in the source code?

Background: Due to political problems, it is necessary to deploy existing platforms (10+) in XX country.

Requirements:

  1. The source code can not have all the keywords and pictures related to the domestic company, all the domain names should be replaced by xx country
  2. The source code can not have Chinese

Demand analysis:

  1. The first point is easy to solve, a branch of XX country, global search code, replacement is over
  2. Second, manual replacement is too stupid and expensive (there are 10+ platforms)

The main problem comes to, how to remove all Chinese in the source code?

  1. First, use NPM run build to pack the dist file. You can remove all the comments (comment removal is configurable by WebPack and is not the focus of this article. You can find the configuration method yourself).
  2. At this point, we need to deal with the source code in addition to comments in Chinese.

Analysis of difficulties:

  1. Difficulty 1: All internal 10+ platforms have access to internationalization. My other article is juejin.cn/post/697385… , so the source code is not simple Chinese, are with $T (using the i18n library), such as

    < p > {{$t (' please enter the query ')}} < / p > content: this. $t (' SQL cannot be empty)Copy the code

    Solution:

    • $t(‘error’); $t(‘error’);
      {"error" : "error", "confirm" : "confirm",... }Copy the code
    • In addition, other language packages such as zh.json and en.json are commented out in the configuration for i18n. If $t(‘ ABC ‘) does not find a value, ABC will be displayed
      • $t(key) could not find the corresponding key-value, you can configure in the branch of xx country to remove this warning
        const i18n = new VueI18n({
          ...
          silentTranslationWarn: true // Turn off the i18N warning on the console
        })
        Copy the code
  2. Difficulty 2: Some third-party libraries will also be packaged, so the Chinese of the third-party libraries may not be completely cleared. For example, the MIN version of iView, which also has Chinese. Some of your own component libraries, for example

    Look at the following, and solve it with difficulty 3

  3. Difficulty 3: May there be some Missing Chinese? Finally, we need to scan the packaged DIST

    Solution: Write a Webpack plugin.

    1. At this point in the packaging lifecycle “emit phase: before generating resources to the Output directory”, execute the plugin (with the comments removed). At this point, you can be sure to scan the packaged DIST.
    2. Then print the Chinese missing fish to the terminal (tell developers). At the same time, it can also be matched with a map, which will be converted into the corresponding value in the plug-in, or without processing, directly turn the Chinese into empty
      constMap = {day:'day'Month:'month',}new myWebpackPlugin(map)
      Copy the code

Why loader and Plugin? There will be the source code below, and after you look at it, you’ll understand the features and differences

Start writing loader and plugin:

  1. Write a loader
  2. Writing a plugin

1. Write a loader

Writing loader is very simple, is to write a JS. It is then imported in webpack.config.js as the loader specification.

  • Webpack passes a source parameter to the loader’s callback function, and you need to return the source. Function, you can do whatever you want with the source

Configure the loader and import loader based on the tool platform: build an internal front-end tool platform

/ / webpack. Config. Js.// Add the following under production, note the path parameter, if en.json is in the current project, it is better to use dynamic path (if online packaging)
if (env.NODE_ENV === 'production') {
    baseConfig.module.rules.push({
        test: /\.vue$/,
        use: {
            loader: '@myCompany/feTools/bin/lan/zhToEn-loader.js'.// Install NPM i-d@mycompany /feTools
            options: {
                // Pay attention to the path parameter. If the en.json is in the current project, it is better to use a dynamic path (if it is packaged online). Make sure your own en.json path is located
                path: path.resolve(__dirname, 'src/langs/en.json')}}})}...Copy the code

Write your own loader: your-loader.js, and put it on the tool platform (internal front-end tool platform) (local development and debugging, can be absolute path introduction)

  • Put it on the tool platform so the rest of the team can use it easily
// your-loader.js

// Help get the option argument passed in webpack.config.js
const getOptions = require('loader-utils').getOptions

module.exports = function (source) {
  const options = getOptions(this)
  const path = options.path || 'src/langs/en.json'
  const map = require(path) // Get the en.json map
  $t('error') => $t('error')
  const reg = /\$t\(.*? \)/g
  source = source.replace(reg, word= > { $t('error') => $t('error')
    if (["'".'"'].includes(word[3]) {const str = word
      word = word.slice(4).slice(0, -2)
      const val = map[word]
      if (val) {
        word = map[word]
        return "$t('" + word + "')"
      } else {
        console.log('No Corresponding English or non-canonical :', str)
      }
    } else {
      return word
    }
  })
  return source
}
Copy the code

In addition, the execution sequence of multiple Loaders is as follows: Execute the last loader first, from last to first

2. Write a plugin

Writing to loader is easier because loader already filters the file type (for example: test: /.vue$/,).

Writing a plugin is more complex than a Loader, and the plugin API is more powerful and can be configured in different packaging lifecyems.

  • We’ll go into a little more detail on how to configure hooks for the packaging lifecycle.

Start by importing the plug-in in webpack.config.js

./ / this is the path of the node_modules introduction file (on tool platform (https://juejin.cn/post/6973845836891586591/)), need to install NPM I - D @ company/feTools
const YourPlugin = require('@company/feTools/bin/lan/your-plugin.js')...// In the production plugin
if (env.NODE_ENV === 'production') {...constMap = {month:'month'That day:'day'
    }
    baseConfig.plugins = (baseConfig.plugins || []).concat([
        ...
        new YourPlugin(map) // Can be added to pass parameters, will be based on the key-value of the scanned Chinese, replaced by English])}...Copy the code

Start to write the plugin

The first step is to write the main structure of a plug-in

// your-plugin.js this is the natural way to write webpack plugins, you can refer to this first

class YourPlugin { 
  apply (compiler) { 
    compiler.hooks.emit.tapAsync('YourPlugin'.(compilation, callback) = >{... }}})module.exports = YourPlugin

// The lifecycle hook function, which is exposed by compiler, can be accessed as follows:
compiler.hooks.someHook.tap(/ *... * /); Analysis on YourPlugin within the compiler. The hooks. Emit. TapAsync tapAsync: () is an asynchronous mode, tap is synchronous, and tapPromise mode, detail to view: HTTPS://github.com/webpack/tapable#hook-typesEmit: is the hook bit for the life cycle.//v4.webpack.docschina.org/api/compiler-hooks/EntryOption executes the plug-in after the Entry configuration item in the WebPack option has been processed. AfterPlugins After setting the initial plug-in, execute the plug-in. After the resolver installation is complete, execute the plug-in. When ready, execute the plug-in. After the afterEnvironment Environment is installed, execute the plug-in. BeforeRun Compiler.run () adds a hook before execution. Parameter: Compiler.../ / in addition, the parameters of the compilation are similar to the compiler, the life cycle of a hook can refer to the website https://v4.webpack.docschina.org/api/compilation-hooks
class YourPlugin { 
  apply (compiler) { 
    // make can trigger compilation.hooks
    compiler.hooks.make.tapAsync('YourPlugin'.(compilation, callback) = > {
        compilation.hooks.buildModule.tap('YourPlugin'.module= > {
            console.log('module.resource'.module.resource);
            console.log('module.loaders'.module.loaders);
            console.time('YourPlugin');
        });

        compilation.hooks.succeedModule.tap('YourPlugin'.module= > {
            console.timeEnd('YourPlugin');
        });

        callback(); Callback (); // If tapAsync is configured, callback() is required}); }}module.exports = YourPlugin
Copy the code

Complete code on

/** * why not use loader? Because to wait until some plugin (TerserPlugin compression, remove comments and so on) completed in the scan * @ effect of the scan Chinese, here mainly solve JS * in addition to CSS file plugin CssMinimizerPlugin remove comments and so on */
const path = require('path')

constObj = {hint:'prompt'To determine:'confirm'Cancellation:'cancel'
}

// Merge the mapMerge object with key-value to replace the Chinese key. Make sure the source code does not have 1 Chinese
const replaceZh = (optionsMap={}, source, name) = > {
  const mapMerge = Object.assign(optionsMap, obj)
  const reg = /[\u4e00-\u9fa5]{1,}/g
  source = source && source.replace(reg, word= > {
    const val = mapMerge[word]
    if (val) {
      console.log(`${word}replace${val}`)
      return val
    } else {
      console.log(name, word)
      return ' '}})return source
}

class YourPlugin {
  constructor (options) {
    this.options = options // Get the pass parameter to the plugin
  }
  apply (compiler) {
    // emit: generates resources before output.
    compiler.hooks.emit.tapAsync('YourPlugin'.(compilation, callback) = > {
      const map = compilation.assets // Change the value directly to take effect
      for (const name in map) {
        if (['.js'].includes(path.extname(name))) {
          // Directly modify map[name]._value to affect the output string
          map[name]._value = replaceZh(this.options, map[name]._value, name)
        }
      }
      callback()
    })
  }
}

module.exports = YourPlugin
Copy the code

Code word is not easy, little praise to encourage ~