Objective: Build a UI scaffolding based on VUE, library loading mode support script tag, modular loading, component introduction mode support on-demand loading and full loading. Scaffolding link

Directory and file conventions

A complete component library scaffold should not only have a proper directory for storing components, but also have a component demo page to facilitate the development and presentation of components, so we need to provide two webPack configurations, one for component packaging, one for component single page application, and then slowly add gulp/rollup.

For debugging purposes, we now plug the package directly into node_modules and change the LIB_ROOT value in build/ conconst. Js as needed.

Correct debug order:

  1. npm install
  2. NPM run build:lib empties the directory first, so put it before build:es
  3. npm run build:es
  4. npm run dev

How do I package class libraries using WebPack

Usually use Webpack to package application more, how to use it to package tool library, UI library

Hidden in the Ouput configuration, WebPack packs both generic web applications and libraries that support a wide variety of environments. LibraryTarget declares the packaged module type. Library is associated with the module export name.

To put it simply, we normally package an application by generating multiple files that provide references to script tags. After the above two fields are configured, the resulting file can be used by script/require. Other configurations are probably the same, but also according to your file type, product requirements to add the corresponding loader, plugin. For example, vue-loader is needed to process. Vue files, and vue-jsx is used. Install @vue/ babel-jsx plugin support, babel-Loader, etc.

output: {
  path: path.resolve(__dirname, '.. / ', LIB_DIR),
  // filename: '[name]/index.js',
  library: 'wind-ui-vue'.libraryTarget: 'umd',},Copy the code

Component library Component development mode, write template and full package

How are components of a component library used

We usually make an application, a component is a. Vue file, when needed import in. However, the development component library is not quite the same, because the component library environment does not have Vue variables, which need to be provided by the developer, so you need to do a layer of wrapping to get the Vue variables provided by the real development environment, each component is wrapped in its own index.js layer. When using the packer, the vue. use function is used here, which calls the install method provided by the object argument. Once we add a registration function to the method, we automatically register the component globally.

        import Button from './Button.vue'
        
        Button.install = Vue= > {
        
          Vue.component(Button.name, Button)
        }
        
        if (typeof window! =undefined && window.Vue) {
          Button.install(Vue)
        }
        
        export default Button
Copy the code
        import Vue from 'vue'
        import { Button } from 'wind-ui-vue'
        Vue.use(Button)
Copy the code

How do I import components in full

Now that each component has its own registration file, it’s just a matter of creating an entry file to import each component.

    import Button from './components/button/index'
    import './components/button/index.scss'
    
    import Card from './components/card/index'
    import './components/card/index.scss'
    
    const install = (Vue) = > {
      Vue.use(Button)
      Vue.use(Card)
    }
    
    if (typeof window! =undefined && window.Vue) {
      install(Vue)
    }
    
    export {
      Button,
      Card
    }
    
    export default {
      install
    }
Copy the code

It’s all packed up

Once the entry file for the full component is written, make it a WebPack entry and you can pack all the components into one big package.

How can components be packaged to support on-demand loading – Webpack multiple portals

Component libraries are getting bigger and bigger. How do you pack each component individually, one package at a time

What is the premise of load on demand?

All components cannot be packaged together. Webpack is one entry for one exit, so we need to generate one entry for each component.

We mainly use the FS module of Node to read and write files, find out the entry file index.js of all components, make it into webpack entry, and then configure the corresponding exit path. We design the directory structure of source code and package code is roughly the same.

const getFiles = function (dirs, fileReg) {

  if (!Array.isArray(dirs)) {
    dirs = [ dirs ]
  }

  return dirs.reduce((arr, dir) = > {

    let files = fs.readdirSync(dir)
    if(! files.length)return [] 
  
    files = files.reduce((arr, file) = > {
      let res = []
      const filePath = path.join(dir, file)
  
      fileReg.test(file) && res.push(filePath)
  
      // if is directory
      if (fs.statSync(filePath).isDirectory()) {
        res = res.concat(getFiles(filePath, fileReg))
      }
  
      return arr.concat(res)
  
    }, [])
  
    return arr.concat(files)

  }, [])
}

const entries =  (() = > {

  let files = getFiles('./src/components', /^index\.js$/)
  files = files.reduce((obj, file) = > {
    return Object.assign(obj, {
      [file.replace('src'.' ').replace('.js'.' ')]: '/' + file
    })
  }, {})
  return files
})()

module.exports = merge(baseConf, {
  mode: 'development'.entry: {
    ...entries,
    'index': './src/index.js'
  },
  output: {
    path: path.resolve(__dirname, '.. / ', LIB_DIR),
    // filename: '[name]/index.js',
    library: 'wind-ui-vue'.libraryTarget: 'umd',},plugins: [
    new CleanWebpackPlugin()
  ]
})
Copy the code

It seems that the configuration of the class library can be a little less complicated than the general application configuration, because there is no need to worry about caching, development packaging efficiency, etc.

How to load on demand?

The premise is that each component has its own file. What’s next? Easy!! Such as the need to import Button component Button from ‘wind – UI – vue/lib/components/Button/index, js’ introduction, and we at ordinary times in the project into the file again. I have to say I’m a little tired of writing so much, and I have to remember the location of the component directory, who goes to node_modules. This is where the babel-import-plugin comes in, which helps us optimize this annoying operation. Look at the documentation.

Did you get it? It is helping us replace the source code!! Convert es6’s import syntax to the appropriate path to find the component. Although it’s not tree-shaking, it’s hiding. But it did help us a lot. My Button is under wind-ui-vue/lib/components instead of wind-ui-vue/lib/components. There is also a field libraryDirectory that declares the component’s path prefix. Just deal with the situation as it is.

Webpack packed file structure

Component styles are separated from component logic and packaged separately — gulP

The styles of different components are packaged separately, and the component’s stylesheet can be packaged separately, not bundled with the component’s JS logic. Take a look at some of these gestures

Load components as needed

Import {Button} from ‘wind-uI-vue’, ideally you want the component’s style to be loaded as well. For vue components, you can write

Question for now: How can style separation be automatically referenced by components? The babel-import-plugin mentioned above provides a solution, as shown in the figure below, where the style field appears to be the directory in which the component’s style sheet is declared (true is the style directory, and other values represent subpaths).

So we put the style file in the style directory of each component. Here we can generate a.js file to import the corresponding.scss file. This file is automatically inserted into the component by the plug-in.

Note that the style files are separate from the component logic, so when the library is packaged, the style files are not processed by Webpack. Is it cleaner? These SCSS files are processed by Webpack only when the application is packaged. The file structure after gulp processing is shown in the figure.

Core code on this, but also in the declaration directory to find all SCSS files for processing.

    gulp.task('esScss', () = > {return scssTask(ES_DIR)
    })
    
    function scssTask (DIR) {
      // Find the stylesheets for all components
      const files = getFiles(['./src/components'], /^index\.scss$/)
      
      files.forEach(file= > {
        const {
          dir,
          base
        } = path.parse(file);
    
        // Copy some common styles of the library
        fse.copySync(
          path.resolve(__dirname, 'src/style'),
          path.resolve(__dirname, DIR, 'style'))// generate a .js file to require matched .scss file in every component directory and copy it
        const destPath = path.join(__dirname,
          DIR,
          dir.replace(/ [^ \] + \ / /.' '))// copy the.scss source file
        fse.copyFileSync(
          path.resolve(__dirname, file),
          path.resolve(
            destPath,
            base
          )
        )
    
        // Generate a js file that introduces the SCSS file for the babel-import-plugin
        mkdirp.sync(path.resolve(destPath, 'style'))
        fs.writeFileSync(path.resolve(destPath, 'style'.'index.js'), `require ('.. /index.scss')`)})}Copy the code

Load the component in full

Import win from ‘wind-ui-vue’, without the help of Babel, we have to manually import all the component styles, one by one in the import file. SCSS file, the same as the import.js file

    import Button from './components/button/index'
    import './components/button/index.scss'
    
    import Card from './components/card/index'
    import './components/card/index.scss'
    
    const install = (Vue) = > {
      Vue.use(Button)
      Vue.use(Card)
    }
    
    if (typeof window! =undefined && window.Vue) {
      install(Vue)
    }
    
    export {
      Button,
      Card
    }
    
    export default {
      install
    }
Copy the code

Considering theumdThis modular solution is used and needs to be provided.cssFile.

  • When components are referenced separately, each component’s style is a separate style file, handled with GULP.
    gulp.task('lib'['libScss'], () = > {return gulp.src('src/**/*.scss')
      .pipe(sass.sync().on('error', sass.logError))
      .pipe(postcss([ autoprefixer() ]))
      .pipe( gulp.dest(LIB_DIR, { sourcemaps: true}})))Copy the code
  • When importing a component in full, package all component styles into one copy.css. Look back at the one where we registered all the componentsindex.jsAll of the component styles are already in the file, so just pull them out.
    • The WebPack wrapper uses the mini-CSs-extract-Plugin (control the build directory yourself)

      const MiniCssExtractPlugin = require('mini-css-extract-plugin');
      
      module.exports = {
          module: {
          rules: [{test: /\.scss$/.use: [{loader: MiniCssExtractPlugin.loader,
                },
                'css-loader'.'postcss-loader',
                {
                  loader: 'sass-loader',}]}]},plugins: [
          new MiniCssExtractPlugin()
        ]
      }
      Copy the code
    • The rollup wrapper uses the rollup-plugin-SCSS plugin (control the build directory itself)

      import scss from 'rollup-plugin-scss'
      module.exports = {
        input: '/' + file,
        output: {
          file: file.replace('src', ES_DIR),
          format: 'esm'
        },
        plugins: [
          scss(),
        ]
      }
      Copy the code

The ES6 module version — rollup — provides the UI library

Modules like the one packaged as a UMD specification above, while providing all the environmental support, are still not introduced by the ES6 syntax import win from ‘wind-ui’, i.e. esModule is not supported. In addition, due to WebPack compatibility considerations, the packaged component adds some of its own redundant compiled code, resulting in redundancy. Is there any way to pack the ES6 version separately? Try rollup

emmmm… The rollup-plugin-vue is equivalent to the ue-loader of webpack, the output. Format is set to esM, and the babel.runtimeHelpers is set to true. Make the package more streamlined.

The idea is consistent with Webpack, but also to find out all the component files, transcoding output. Rollup has a larger output file size because the target is more simple. It’s complementary to Webpack.

import commonjs from 'rollup-plugin-commonjs' 
import VuePlugin from 'rollup-plugin-vue'
import resolve from '@rollup/plugin-node-resolve';
import babel from 'rollup-plugin-babel';
import scss from 'rollup-plugin-scss'

const babelConf = require('./babel.config')

const { getFiles } = require('./build/util')
const { ES_DIR } = require('./build/const')

const entries =  (() = > {

  let files = getFiles('./src/components', /^index\.js$/)
  console.log('files', files)
  files.push('src/index.js')
  files = files.map(file= > {
    return {
      input: '/' + file,
      output: {
        file: file.replace('src', ES_DIR),
        format: 'esm'
      },
      // .replace('.js', '')
      plugins: [
        scss(),
        resolve(),
        babel({
          runtimeHelpers: true.exclude: 'node_modules/**'
        }),
        commonjs(),
        VuePlugin()
      ]
    }
  })
  console.log('files', files)
  return files
})()

module.exports = entries
Copy the code

After NPM run build:es, the Lib directory generated by Webpack has an es directory at the same level. Of course, the generated resources can also be based on the actual situation of more play.

Use the UI library on your project

To see the effect on the demo page, the source code is at site/ SRC /main.js (omitting the presentation introduced by the

// Full import of esModule mode
import win from 'wind-ui-vue/es'
import 'wind-ui-vue/es/index.css'
Vue.use(win)
Copy the code
// CommonJS Module full import
const win = require('wind-ui-vue').default
import 'wind-ui-vue/lib/index.css'
Vue.use(win)
Copy the code
// Load as needed, with 'babel-import-plugin' used
// The Card component is not packaged, the Button component style is automatically loaded
import { Button, Card } from 'wind-ui-vue'
Vue.use(Button)
Copy the code

The resources

  • Vant – UI before the project used Vant, this component library scaffolding ideas also refer to Vant, thank open source