This article is a continuation of the previous article “from element-UI source chat build UI library series 2”,

The previous article focused on parsing all commands of script scripts. This article continues the analysis of makefiles and other script commands.

This article was first published on the public account “Full stack big guy’s Cultivation road”, welcome to follow.

Sixth, the makefile

The benefit of makefiles is “automated compilation”. Once written, only one make command is required, and the whole project is completely compiled automatically, greatly improving the efficiency of software development. Make is a command tool that interprets instructions in makefiles. In general, most ides have this command, such as Delphi make, Visual C++ nmake, and GNU Linux make. As you can see, makefiles become a compilation method for engineering purposes.

NPM run dev = NPM run dev = NPM run dev

PHONY represents a fake target, not a real file target. Note that the target of a Makefile is a file by default.
.PHONY: dist test
Make defaults to help
default: help
# Build theme
# build all theme
build-theme:
    npm run build:theme
# install dependencies
install:
    npm install
# Use Taobao source to install dependencies
install-cn:
    npm install --registry=http://registry.npm.taobao.org
# Build the app
dev:
    npm run dev

play:
    npm run dev:play
# Add component
new:
    node build/bin/new.js $(filter-out $@,$(MAKECMDGOALS))
Add a new language to the site
new-lang:
    node build/bin/new-lang.js $(filter-out $@,$(MAKECMDGOALS))
# packaged
dist: install
    npm run dist
# deployment
deploy:
    @npm run deploy
# release
pub:
    npm run pub
# Unit tests
test:
    npm run test:watch
# Help message
help:
    @echo "\033[35mmake\033[0m \033[1m Command Instructions \033[0m"
    @echo "\033[35mmake install\033[0m\t\033[0m\t\033[0m\t\033[0m\t \033[0m\t\033] -- Install dependency"
    @echo "\033[35mmake new 
      
        [中文名]\033[0m\t-- create new component package. E.g. 'make new button '"
      
    @echo "\ [033 35 mmake dev \ [0 033 m 033 [0 m \ \ t \ \ t 033 [0 m \ t \ [0 m \ t - 033 development mode"
    @echo "\033[35mmake dist\033[0m\t\033[0m\t\033[0m\t\033[0m\t \033[0m\t\033[0m\t\033[0m\t\033[0m\t \
    @echo "\033[35mmake deploy\033[0m\t\033[0m\t\033[0m\t\033[0m\t \033[0m\t- deploy demo"
    @echo "\033[35mmake pub\033[0m\t\033[0m\t\033[0m\t\033[0m\t \033[0m\t\033[0m\t\033[0m\t\033] -- post to NPM"
    @echo "\033[35mmake new-lang 
      
       \033[0m\t\033[0m\t\033[0m\t \033[0m\t\033[0m\t\033[0m\t \t-- Add new language for website. For example, 'make new-lang fr'"
      
Copy the code

All script commands have been analyzed except make new and make new-lang.

make new

node build/bin/new.js $(filter-out $@,$(MAKECMDGOALS))

You can run this command to add a component.

You can execute Node.build bin new.js aaa to generate aaa components much faster.

Add a new component aaa and the advantages come out:

  1. Create a new component directory under the/Packages directory and finish building the directory structure/Packages/AAA
  2. Create the component document, /examples/docs/{lang}/aaa.md
  3. Create component unit test files, / test/unit/specs/aaa. Spec. Js
  4. Create a component style file, /packages/theme-chalk/ SRC/aaA.scss
  5. Create a component type declaration file, /types/aaa.d.ts
  6. Configuration: In/components. The components that is configured in the json file information, in/examples/nav. Config. This component is added to the json, the routing configuration, The component’s style file is automatically introduced in /packages/theme-chalk/ SRC /index.scss and the type declaration file is automatically introduced in /types/element-ui.d.ts

Component AAA is generated as follows:

So how does that work?

Let’s look at the implementation:

'use strict';


console.log();
process.on('exit'.() = > {
  console.log();
});
// Unfilled throws an exception
if(! process.argv[2]) {
  console.error('[Component name] Mandatory - Please enter new Component name');
  // The program exits
  process.exit(1);
}

const path = require('path');
const fs = require('fs');
/ / write file
const fileSave = require('file-save');
// Uppercase
const uppercamelcase = require('uppercamelcase');
// Component name
const componentname = process.argv[2];
// The component's Chinese name
const chineseName = process.argv[3] || componentname;
const ComponentName = uppercamelcase(componentname);
console.log(ComponentName)
const PackagePath = path.resolve(__dirname, '.. /.. /packages', componentname);
const Files = [
  // Register the component by writing index.js to SRC /main
  {
    filename: 'index.js'.content: `import ${ComponentName} from './src/main';
    
    /* istanbul ignore next */
    ${ComponentName}.install = function(Vue) {
      Vue.component(${ComponentName}.name, ${ComponentName});
    };
    
    export default ${ComponentName}; `
  },
  // Create a new template under SRC /main of the XXX component in Packages
  {
    filename: 'src/main.vue'.content: `<template>
    <div class="el-${componentname}"></div>
</template>

<script>
export default {
  name: 'El${ComponentName}'}; </script>`
  },
  // Documents in four languages
  {
    filename: path.join('.. /.. /examples/docs/zh-CN'.`${componentname}.md`),
    content: ` # #${ComponentName} ${chineseName}`
  },
  {
    filename: path.join('.. /.. /examples/docs/en-US'.`${componentname}.md`),
    content: ` # #${ComponentName}`
  },
  {
    filename: path.join('.. /.. /examples/docs/es'.`${componentname}.md`),
    content: ` # #${ComponentName}`
  },
  {
    filename: path.join('.. /.. /examples/docs/fr-FR'.`${componentname}.md`),
    content: ` # #${ComponentName}`
  },
  // Create a unit test for the component under test
  {
    filename: path.join('.. /.. /test/unit/specs'.`${componentname}.spec.js`),
    content: `import { createTest, destroyVM } from '.. /util'; import${ComponentName} from 'packages/${componentname}';

describe('${ComponentName}', () => {
  let vm;
  afterEach(() => {
    destroyVM(vm);
  });

  it('create', () => {
    vm = createTest(${ComponentName}, true);
    expect(vm.$el).to.exist;
  });
});
`
  },
  // Create a theme style for the component
  {
    filename: path.join('.. /.. /packages/theme-chalk/src'.`${componentname}.scss`),
    content: `@import "mixins/mixins";
@import "common/var";

@include b(${componentname}`) {}
  },
  // Create a type declaration for the component
  {
    filename: path.join('.. /.. /types'.`${componentname}.d.ts`),
    content: `import { ElementUIComponent } from './component'

/** ${ComponentName} Component */
export declare class El${ComponentName} extends ElementUIComponent {
}`}];// Add to components.json
const componentsFile = require('.. /.. /components.json');
if (componentsFile[componentname]) {
  console.error(`${componentname}Existing. `);
  process.exit(1);
}
componentsFile[componentname] = `./packages/${componentname}/index.js`;
fileSave(path.join(__dirname, '.. /.. /components.json'))
  .write(JSON.stringify(componentsFile, null.' '), 'utf8')
  .end('\n');

// Add to index.scss
const sassPath = path.join(__dirname, '.. /.. /packages/theme-chalk/src/index.scss');
const sassImportText = `${fs.readFileSync(sassPath)}@import "./${componentname}.scss"; `;
fileSave(sassPath)
  .write(sassImportText, 'utf8')
  .end('\n');

// Add to elder-ui.d.ts
const elementTsPath = path.join(__dirname, '.. /.. /types/element-ui.d.ts');

let elementTsText = `${fs.readFileSync(elementTsPath)}/ * *${ComponentName} Component */
export class ${ComponentName} extends El${ComponentName}{} `;

const index = elementTsText.indexOf('export') - 1;
const importString = `import { El${ComponentName} } from './${componentname}'`;

elementTsText = elementTsText.slice(0, index) + importString + '\n' + elementTsText.slice(index);

fileSave(elementTsPath)
  .write(elementTsText, 'utf8')
  .end('\n');

/ / create a package
Files.forEach(file= > {
  fileSave(path.join(PackagePath, file.filename))
    .write(file.content, 'utf8')
    .end('\n');
});

// Add to nav.config.json
const navConfigFile = require('.. /.. /examples/nav.config.json');

Object.keys(navConfigFile).forEach(lang= > {
  let groups = navConfigFile[lang][4].groups;
  groups[groups.length - 1].list.push({
    path: ` /${componentname}`.title: lang === 'zh-CN'&& componentname ! == chineseName ?`${ComponentName} ${chineseName}` : ComponentName
  });
});

fileSave(path.join(__dirname, '.. /.. /examples/nav.config.json'))
  .write(JSON.stringify(navConfigFile, null.' '), 'utf8')
  .end('\n');

console.log('DONE! ');
Copy the code

make new-lang

node build/bin/new-lang.js $(filter-out $@,$(MAKECMDGOALS))

Run this command to add a new language

'use strict';

console.log();
process.on('exit'.() = > {
  console.log();
});

if(! process.argv[2]) {
  console.error('[language] is required! ');
  process.exit(1);
}

var fs = require('fs');
const path = require('path');
const fileSave = require('file-save');
const lang = process.argv[2];
// const configPath = path.resolve(__dirname, '.. /.. /examples/i18n', lang);

// Add to components.json
const componentFile = require('.. /.. /examples/i18n/component.json');
if (componentFile.some(item= > item.lang === lang)) {
  console.error(`${lang} already exists.`);
  process.exit(1);
}
let componentNew = Object.assign({}, componentFile.filter(item= > item.lang === 'en-US') [0], { lang });
componentFile.push(componentNew);
fileSave(path.join(__dirname, '.. /.. /examples/i18n/component.json'))
  .write(JSON.stringify(componentFile, null.' '), 'utf8')
  .end('\n');

// Add to page.json
const pageFile = require('.. /.. /examples/i18n/page.json');
let pageNew = Object.assign({}, pageFile.filter(item= > item.lang === 'en-US') [0], { lang });
pageFile.push(pageNew);
fileSave(path.join(__dirname, '.. /.. /examples/i18n/page.json'))
  .write(JSON.stringify(pageFile, null.' '), 'utf8')
  .end('\n');

// Add to route.json
const routeFile = require('.. /.. /examples/i18n/route.json');
routeFile.push({ lang });
fileSave(path.join(__dirname, '.. /.. /examples/i18n/route.json'))
  .write(JSON.stringify(routeFile, null.' '), 'utf8')
  .end('\n');

// Add to nav.config.json
const navFile = require('.. /.. /examples/nav.config.json');
navFile[lang] = navFile['en-US'];
fileSave(path.join(__dirname, '.. /.. /examples/nav.config.json'))
  .write(JSON.stringify(navFile, null.' '), 'utf8')
  .end('\n');

// create a new folder under docs
try {
  fs.statSync(path.resolve(__dirname, `.. /.. /examples/docs/${ lang }`));
} catch (e) {
  fs.mkdirSync(path.resolve(__dirname, `.. /.. /examples/docs/${ lang }`));
}

console.log('DONE! ');
Copy the code

7. Other engineering scripts

md-loader

In addition to the MD-loader, the script below build is basically analyzed again.

Speaking of mD-Loader, the docs and demos on the website are thanks to him.

It is a loader, which is responsible for most of the component demo plus documentation mode on the component page.

Can be in/examples/route. Config. See registerRoute js method generated page routing configuration, loadDocs method is used to load/examples/docs / {lang} / comp., md.

Note that the Markdown document loaded here, rather than the usual Vue file, can be rendered as a VUE component on the page just like a Vue file. How can this be done?

As we know, the idea of Webpack is that all resources can be required, just by configuring the appropriate loader. Module. rules in /build/webpack.demo.js file can be seen in the processing of markdown rules, first through the mD-loader processing markdown file, from which the vUE code parsing. It is then handed to vue-loader, which eventually generates vUE single file components to render to the page. This allows you to see the documentation of the component page and the component demo presentation.

/ build/webpack. Demo. Js:

      {
        test: /\.md$/,
        use: [
          {
            loader: 'vue-loader'.options: {
              compilerOptions: {
                preserveWhitespace: false}}}, {loader: path.resolve(__dirname, './md-loader/index.js')}}],Copy the code

For details on how to parse MarkDown into vUE components, read The Element team’s Talk about MarkDown Parsing in Element Documents.

config.js

Common configuration for Webpack, such as externals, alias, and so on. Through the configuration of externals, part of the component library code redundancy problem is solved, such as component and component library common module code, but the component style redundancy problem has not been solved; The Alias alias configuration provides convenience for developing component libraries.

// Webpack common configuration, such as externals and alias
var path = require('path');
var fs = require('fs');
var nodeExternals = require('webpack-node-externals');
var Components = require('.. /components.json');

Object.keys(Components).forEach(function (key) {
  externals[`element-ui/packages/${key}`] = `element-ui/lib/${key}`;
});
// Externals addresses code redundancy when components depend on other components and are imported on demand
// For example, the Table component relies on the Checkbox component. If I introduce both Table and Checkbox in the project, I will not generate redundant code
// If you do not have the following, you will see two copies of the Checkbox component code.
// Redundant code also occurs for common locales, utils, mixins, transitions, etc
// But with externals, it tells WebPack that it does not need to pack these imported packages into the bundle and then run from outside
// Get these extension dependencies. The compiled table.js dependency on the Checkbox component is introduced in the packaged /lib/tables.js:
// module.exports = require("element-ui/lib/checkbox")
// There is no redundant JS code, but for the CSS part, element-UI does not handle redundancy.
// You can see the checkbox component in /lib/theme-chalk/table. CSS and /lib/theme-chalk/checkbox. CSS

externals['element-ui/src/locale'] = 'element-ui/lib/locale';
utilsList.forEach(function (file) {
  file = path.basename(file, '.js');
  externals[`element-ui/src/utils/${file}`] = `element-ui/lib/utils/${file}`;
});
mixinsList.forEach(function (file) {
  file = path.basename(file, '.js');
  externals[`element-ui/src/mixins/${file}`] = `element-ui/lib/mixins/${file}`;
});
transitionList.forEach(function (file) {
  file = path.basename(file, '.js');
  externals[`element-ui/src/transitions/${file}`] = `element-ui/lib/transitions/${file}`;
});

externals = [Object.assign({
  vue: 'vue'
}, externals), nodeExternals()];

exports.externals = externals;
// Set the alias
exports.alias = {
  main: path.resolve(__dirname, '.. /src'),
  packages: path.resolve(__dirname, '.. /packages'),
  examples: path.resolve(__dirname, '.. /examples'),
  'element-ui': path.resolve(__dirname, '.. / ')};exports.vue = {
  root: 'Vue'.commonjs: 'vue'.commonjs2: 'vue'.amd: 'vue'
};

exports.jsexclude = /node_modules|utils\/popper\.js|utils\/date\.js/;
Copy the code

conclusion

Element’s overall architecture is really great, and it is worth learning and applying in development. This involves adding new components, adding new languages, building applications, packaging and compiling applications, publishing applications, unit testing, and so on. For example, after adding new components, the script can help developers to solve the problem of creating new components directory, documents in four languages, configuration of the official website display files, etc.

Instead of changing multiple files each time a new component is created, developers simply need to write the logic for specific components. Remember, in the process of building a project, you can use scripts to solve things, never manual solution.

link

Build UI Library from Element-UI source code series 1

“Build UI library from Element-UI source code series 2”

reference

The build process for ElementUI

See the front-end UI library design from Element UI source code construction process

How to quickly build your own component library (part 1) for your team — Element source architecture

This article was first published on the public account “Full stack big guy’s Cultivation road”, welcome to follow.

Finally, I hope you must point to like three times.

More articles are in my blog address