preface

This section focuses on air-UI packaging. Air-ui is built in three environments:

  1. Local development environmentdev
  2. The component packagedist
  3. Pack pub and tagpub

This section mainly talks about the first two ways, dev and dist. As for the last way, PUB, because there are many pits, we will discuss it in another section.

Dev build

The instructions went something like this:

yarn start
Copy the code

Dev dev dev dev dev dev dev dev dev dev dev

"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js".Copy the code

I just want to do some work on the home.vue file (see air-ui (10) — vuepress write document (advanced version) for the optimization of homv.vue), so I use the start command instead:

"start": "gulp homeVue && npm run dev".Copy the code

So essentially the Dev build doesn’t need to be tweaked at all, just the default scaffolding strip. And the home page is only home.vue. There is no need to manage vue-router. So the webpack.dev.conf.js file doesn’t need to be tweaked at all. In addition to webpack.base.conf.js you need to add this configuration to the resolve alias:

// Set the root directory to air-ui. This is not set to reference absolute paths
'air-ui': path.resolve(__dirname, '.. / '),
Copy the code

Other files that come with the build directory do not need to be adjusted.

About eslint

Because vue-CLI scaffolding comes with esLint checking tools by default. So sometimes the format is strict, so you can use.eslintrc.js to filter some rules. For example, I filtered some checks in the rules map:

  rules: {
    // There is no need to check for Spaces before braces
    "template-curly-spacing": 0.// There is no need to check for unwanted calls
    "no-useless-call": 0.// No need to check the callback syntax
    "no-callback-literal": 0.// There is no need to analyze undefined
    "no-use-before-define": 0.// There is no need to parse useless returns
    "no-useless-return": 0.// There is no need to analyze unnecessary escape characters
    "no-useless-escape": 0.// Use a semicolon or not
    'semi': 0.// Do not require a space between the method name and scratch
    "space-before-function-paren": 0.// allow async-await
    'generator-star-spacing': 'off'.// allow debugger during development
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
  },
Copy the code

You can also disable esLint checking more aggressively by commenting out the useEslint line in the module in webpack.base.conf.js:

  module: {
    rules: [
      / /... (config.dev.useEslint ? [createLintingRule()] : []),
      {
        test: /\.vue$/.loader: 'vue-loader'.options: vueLoaderConfig
      },
Copy the code

This will not trigger esLint checks.

And if you want to ignore certain files or folders directly, you can also ignore them in the.eslintignore file:

/build/
/config/
/dist/
/*.js
/test/unit/coverage/
src/utils/popper.js
src/utils/date.js
Copy the code

For example, I ignored the above files (including two third-party libraries popper and Date that I slightly modified) so that they would not be checked for ESLint.

Dist build

Dev build didn’t what to say, then we talk about dist build, is actually will air – to build the UI component library file, and it is divided into two types of packaging:

  1. Package as universalair-ui.common.jsFor global import
  2. Individual components are packaged into their own JS files for loading and importing on demand

Let’s look at the instructions:

"dist": "npm run clean && webpack --config build/webpack.common.js && webpack --config build/webpack.component.js && npm run css  && npm run lang && npm run theme".Copy the code

Dist webpack is 3.x version, if it is 4.x version, it is not applicable and needs to be adjusted again

Mainly divided into several steps, we will analyze the following:

1. Clean up before packing

npm run clean
Copy the code
"clean": "rimraf lib && rimraf test/**/coverage".Copy the code

This is mainly to clean up the file before packaging, that is, to delete the target directory lib.

2. Pack the common file

webpack --config build/webpack.common.js
Copy the code

The next step is to package the common file. The entry file is components/index.js. The code for webpack.common.js is as follows:

const path = require('path');
const webpack = require('webpack');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const nodeExternals = require('webpack-node-externals');

module.exports = {
  entry: {
    app: ['./src/components/index.js']},output: {
    path: path.resolve(process.cwd(), './lib'),
    publicPath: '/dist/'.filename: 'air-ui.common.js'.chunkFilename: '[id].js'.libraryTarget: 'commonjs2'
  },
  resolve: {
    extensions: ['.js'.'.vue'.'.json'].alias: {
      main: path.resolve(__dirname, '.. /src'),
      'air-ui': path.resolve(__dirname, '.. / ')},modules: ['node_modules']},// Do not pack some files, otherwise the size of common will become very large, especially the language files. The consequence is that if common does not include these files, the files will be typed separately and the path will not change, and if not packaged, the reference will become an absolute path reference and cannot be used again
  externals: [Object.assign({
    vue: 'vue'
  }, {
    '.. /lang/zh-CN': 'air-ui/lib/lang/zh-CN'
  }), nodeExternals()],
  module: {
    rules: [{test: /\.(jsx? |babel|es6)$/.include: process.cwd(),
        exclude: /node_modules|utils\/popper\.js|utils\/date.\js/.loader: 'babel-loader'
      },
      {
        test: /\.vue$/.loader: 'vue-loader'.options: {
          preserveWhitespace: false}}, {test: /\.json$/.loader: 'json-loader'
      },
      {
        test: /\.css$/.loaders: ['style-loader'.'css-loader'.'postcss-loader'] {},test: /\.scss$/.loaders: ['style-loader'.'css-loader'.'sass-loader'] {},test: /\.html$/.loader: 'html-loader? minimize=false'
      },
      {
        test: /\.otf|ttf|woff2? |eot(\? \S*)? $/.loader: 'url-loader'.query: {
          limit: 10000.name: path.posix.join('static'.'[name].[hash:7].[ext]')}}, {test: /\.svg(\? \S*)? $/.loader: 'url-loader'.query: {
          limit: 10000.name: path.posix.join('static'.'[name].[hash:7].[ext]')}}, {test: /\.(gif|png|jpe? g)(\? \S*)? $/.loader: 'url-loader'.query: {
          limit: 10000.name: path.posix.join('static'.'[name].[hash:7].[ext]'}}]},plugins: [
    new ProgressBarPlugin(),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')}),new webpack.LoaderOptionsPlugin({
      minimize: true}})];Copy the code

The logic is simple. Components /index.js is packaged as ES6 Module load. Package it as air-ui.mon.js and put it in the lib directory. So we need to load air-ui completely. We need to load air-ui to ensure that air-ui is indexed in the following way:

import AirUI from 'air-ui'
Copy the code

So we need to add this field to package.json:

"main": "lib/air-ui.common.js".Copy the code

Air-ui library default import file retrieved by the project points to the air-ui.mon.js file.

3. Pack each component

webpack --config build/webpack.component.js
Copy the code

The above task is to package the common file, just to complete the introduction. But sometimes we also need to implement component parts, so we also need to package the components we feel we need, in the root directory component.json:

{
  "locale": "./src/locale/index.js"."button": "./src/components/button/index.js"."button-group": "./src/components/button-group/index.js"."row": "./src/components/row/index.js"."col": "./src/components/col/index.js". Omit N components in the middle"icon": "./src/components/icon/index.js". Omit N components}Copy the code

We don’t need to package and export everything we do separately, just select the components that we think we might need to reference separately later. Let’s take a look at the main logical webpack.conponent.js code:

const path = require('path');
const webpack = require('webpack');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
// Build separate components for separate reference patterns
const Components = require('.. /components.json');
const nodeExternals = require('webpack-node-externals');

const webpackConfig = {
  entry: Components,
  output: {
    path: path.resolve(process.cwd(), './lib'),
    publicPath: '/dist/'.filename: '[name].js'.chunkFilename: '[id].js'.libraryTarget: 'commonjs2'
  },
  resolve: {
    extensions: ['.js'.'.vue'.'.json'].alias: {
      main: path.resolve(__dirname, '.. /src'),
      'air-ui': path.resolve(__dirname, '.. / ')},modules: ['node_modules']},// Do not pack some files, otherwise the size of common will become very large, especially the language files. The consequence is that if common does not include these files, the files will be typed separately and the path will not change, and if not packaged, the reference will become an absolute path reference and cannot be used again
  externals: [Object.assign({
    vue: 'vue'
  }, {
    '.. /lang/zh-CN': 'air-ui/lib/lang/zh-CN'.// Note that locale path substitutions are only required when packaging individual components, because loading components separately also requires multilingual support, not when packaging common, because they are integrated
    '.. /.. /.. /.. /src/locale': 'air-ui/lib/locale'.'.. /locale': 'air-ui/lib/locale',
  }), nodeExternals()],
  module: {
    rules: [{test: /\.(jsx? |babel|es6)$/.include: process.cwd(),
        exclude: /node_modules|utils\/popper\.js|utils\/date.\js/.loader: 'babel-loader'
      },
      {
        test: /\.vue$/.loader: 'vue-loader'.options: {
          preserveWhitespace: false}}, {test: /\.json$/.loader: 'json-loader'
      },
      {
        test: /\.css$/.loaders: ['style-loader'.'css-loader'.'postcss-loader'] {},test: /\.scss$/.loaders: ['style-loader'.'css-loader'.'sass-loader'] {},test: /\.html$/.loader: 'html-loader? minimize=false'
      },
      {
        test: /\.otf|ttf|woff2? |eot(\? \S*)? $/.loader: 'url-loader'.query: {
          limit: 10000.name: path.posix.join('static'.'[name].[hash:7].[ext]')}}, {test: /\.svg(\? \S*)? $/.loader: 'url-loader'.query: {
          limit: 10000.name: path.posix.join('static'.'[name].[hash:7].[ext]')}}, {test: /\.(gif|png|jpe? g)(\? \S*)? $/.loader: 'url-loader'.query: {
          limit: 10000.name: path.posix.join('static'.'[name].[hash:7].[ext]'}}]},plugins: [
    new ProgressBarPlugin(),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')}),new webpack.LoaderOptionsPlugin({
      minimize: true}})];module.exports = webpackConfig;
Copy the code

It’s similar to typing the common file, except that the entry file is a component inside components. Json. So the generated js file is the corresponding component.

4. Packaging CSS

npm run css
Copy the code
"css": "gulp buildCss".Copy the code

This is a gulp task, the logic is very simple, after the CSS generation, move to the lib/style directory:

var sass = require('gulp-sass');
var autoprefixer = require('gulp-autoprefixer');
var cssmin = require('gulp-cssmin');

gulp.task('compile'.function () {
  return gulp.src('./src/styles/*.scss')
    .pipe(sass.sync())
    .pipe(autoprefixer({
      cascade: false
    }))
    .pipe(cssmin())
    .pipe(gulp.dest('./lib/styles'));
});

gulp.task('copyfont'.function () {
  return gulp.src('./src/styles/fonts/**')
    .pipe(gulp.dest('./lib/styles/fonts'));
});

gulp.task('buildCss'['compile'.'copyfont']);
Copy the code

5. Pack language files

npm run lang
Copy the code
"lang": "gulp copylang".Copy the code

This is also a gulp task, which is to move SRC /lang to lib/lang:

gulp.task('copylang'.function () {
  return gulp.src('./src/lang/**')
    .pipe(gulp.dest('./lib/lang'));
});
Copy the code

6. Pack theme files

npm run theme
Copy the code
"theme": "gulp theme".Copy the code

This is also a gulp task:

var themeMapTaskList = {};
var initThemeMap = function () {
  // Delete the old theme first
  themeMapTaskList['del-old-theme'] = gulp.task('del-old-theme', cb => {
    return del([
      './lib/theme'
    ], cb);
  });
  // Read files for all current topics
  var files = fs.readdirSync(path.resolve(`src/theme/`));
  // Iterate over the list of files read
  files.forEach(function (filename) {
    console.log(filename);
    var fileStr = fs.readFileSync(path.resolve(`src/theme/${filename}`));
    var tempMap = {};
    fileStr.toString().replace(/ (+) : (. +); /g.function (match, p1, p2) {
      tempMap[p1.trim()] = p2.trim();
    });
    var themeName = filename.split(".") [0];
    console.log(`${themeName}: ` + JSON.stringify(tempMap));
    var tmpTaskList = {};
    // First copy the old one
    tmpTaskList[`${themeName}-theme-copy`] = gulp.task(`${themeName}-theme-copy`.function () {
      return gulp.src('./src/styles/**')
        .pipe(gulp.dest(`./lib/theme/tmp/${themeName}`));
    });
    // Replace the contents of var
    tmpTaskList[`${themeName}-theme-replace`] = gulp.task(`${themeName}-theme-replace`.function () {
      return gulp.src(`./lib/theme/tmp/${themeName}/common/var.scss`)
        .pipe(replace(/ (+) : (. +); /g.function (match, p1, p2) {
          p1 = p1.trim();
          if (tempMap[p1]) {
            console.log(`theme replace: key: ${p1}, before: ${p2}, after: ${tempMap[p1]}`);
            return `${p1}: ${tempMap[p1]}`
          }
          return match;
        }))
        .pipe(gulp.dest(`./lib/theme/tmp/${themeName}/common`));
    });
    // Regenerate the CSS
    tmpTaskList[`${themeName}-theme-compile`] = gulp.task(`${themeName}-theme-compile`.function () {
      return gulp.src(`./lib/theme/tmp/${themeName}/*.scss`)
        .pipe(sass.sync())
        .pipe(autoprefixer({
          cascade: false
        }))
        .pipe(cssmin())
        .pipe(gulp.dest(`./lib/theme/${themeName}`));
    });
    / / copy the font
    tmpTaskList[`${themeName}-theme-font`] = gulp.task(`${themeName}-theme-font`.function () {
      return gulp.src(`./lib/styles/fonts/**`)
        .pipe(gulp.dest(`./lib/theme/${themeName}/fonts`));
    });
    // Finally string together the tasks for this topic
    themeMapTaskList[`${themeName}-theme`] = gulp.task(`${themeName}-theme`, seq.apply(null, _.keys(tmpTaskList)));
  });
  // Delete TMP directory
  themeMapTaskList['del-theme-tmp'] = gulp.task('del-theme-tmp', cb => {
    return del([
      './lib/theme/tmp'
    ], cb);
  });
};

initThemeMap();
gulp.task('theme', seq.apply(null, _.keys(themeMapTaskList)));
Copy the code

This task is complicated, and the specific logic can be seen in this article about the self-built VUE component AIR-UI (15) — Theme customization

conclusion

This way the component library is packed and the directory looks like this:

Air UI (1) — Why I built an Element UI component is explained. In the next section, we’ll look at how air-UI implements theme customization and how it differs from Element-UI.


Series of articles:

  • Air-ui (1) — Why do I need to build an Element UI component
  • Self-built VUE component AIR-UI (2) — Take a look at the Element UI project
  • Self-built VUE component AIR-UI (3) – CSS development specification
  • Air-ui (4) — Air-UI environment setup and directory structure
  • Air-ui (5) — Create the first vUE component, Button
  • Self-built VUE component AIR-UI (6) – Creates built-in service components
  • Build vUE component AIR-UI (7) – Create command component
  • Self-built VUE component AIR-UI (8) — Implementation part introduces components
  • Build your own VUE component air-UI (9) — document with Vuepress
  • Air-ui (10) — Vuepress Documentation (Advanced version)
  • Vue Component Air-UI (11) — Vuepress Documentation (Crawl version)
  • Self-built VUE component AIR-UI (12) — Internationalization mechanism
  • Self-built VUE Component AIR-UI (13) — Internationalization Mechanism (Advanced Version)
  • Self-built VUE component AIR-UI (14) — Packaged Build (Dev and Dist)
  • Self-built VUE component AIR-UI (15) — Theme customization
  • Self-built VUE component AIR-UI (16) – Packages to build pub tasks
  • Build your own VUE component AIR-UI (17) – Develop a pit crawl and summary