【 Write in front 】

In recent work, there are constantly scenes, which require a scaffold for rapid prototype development and idea verification. After trying open source tools like VUe-CLI, I still feel a little diff from my expectations. So I came up with the idea of building a scaffold that was light enough and fast enough. It’s fast to download, fast to install, fast to pack and build, fast to run, and most importantly, you can diy yourself at any time.

[See here]

First of all, the most commonly used technology stack is vUE based, and then seeing webPack recently updated to version 5, the speed of packaging builds has been greatly improved. So build your own scaffolding based on webpack5. Before starting code, I sketched what it would look like and what it would be capable of. It should end up like this:

Basic version The standard version
dev-server

Dealing with HTML/JS/VUE/CSS/LESS/SASS/IMG/JPG and other files

The routing configuration

The mock data

The agent interface

Packaging analysis

Unit testing

Compile build
dev-server

Dealing with HTML/JS/VUE/CSS/LESS/SASS/IMG/JPG and other files

The mock data

The agent interface

Packaging analysis

Unit testing

Compile build

Basic layout

Navigation configuration

Support for TS

Support the Markdown

Style to check

Well,, start with the basic version.

// Initialize yarn init // Install webpack dependencies YARN add webpack webpack-cli webpack-dev-server webpack-merge --devCopy the code

To explain, webpack-CLI is designed to support direct invocation of webpack commands in scripts. Webpack-dev-server is a server service that provides development. Webpack-merge can merge multiple Webpack configurations.

Mkdir test mock build SRC // For different scenarios, There should be a different packaging configuration touch the build/webpack config. Base. Js build/webpack config. Dev. Js build/webpack config. Prod. Js / / a common file, Extract some common configuration touch build/config.jsCopy the code

At this point, the directory structure of the file looks like this:

Continue adding dependencies

Yarn add vue vuex // add a tool library, network library, Yarn add loDash AXIos view-design // Install polyfill to handle ES6 + syntax YARN add @babel/ core@babel /polyfill babel-loaderCopy the code

Webpack needs to specify an entry file to build the dependency tree, we use SRC /main.js as the entry file.

// src/main.js
import '@babel/polyfill';
import Vue from 'vue';
import VueRouter from 'vue-router';
import ViewUI from 'view-design';
import routes from './common/router';
import ApiClient from './common/client';
import 'view-design/dist/styles/iview.css';

Vue.use(VueRouter);
Vue.use(ViewUI);
Vue.prototype.$client = new ApiClient();

const router = new VueRouter({routes});

const app = new Vue({
  router
}).$mount('#app');
Copy the code

VueRouter and ViewUI are installed using vue.use (), which calls VueRouter’s internal install method. Note that the ViewUI style file needs to be imported separately. In addition, note that there is something called ApiClient, which is a Client that encapsulates a network request and is mounted to the Vue prototype chain, as explained later. A Vue instance needs a mount point, and a single page application needs an HTML file to render page content. So we need to edit another index.html to provide the basic container.

<! DOCTYPEhtml>
<html>
    <head>
        <meta charset="utf-8">
        <title>Zero-X</title>
    </head>
    <body>  
        <div id="app">
            <router-view></router-view>
        </div>     
    </body>
</html>
Copy the code

The file is simple and provides oneid="app"And a mount point for switching routesrouter-viewThe label. But we are actually dynamically generated HTML files when we package. Why is that? The principle of single-page application is to specify a label as a container and dynamically switch its content, that is, dynamically mount/remove its child nodes to complete the change of page content. And can cooperate to complete the control logic of the page dynamic switch child node, the corresponding is the routing plug-in. The control logic, style, and so on are packaged into individual files and inserted into THE HTML for the first loading and rendering of the page. It should end up like this:

You can see that some JS files and CSS style files have been inserted into the page in the most familiar primitive way, loaded with script and link tags. A plugin called htML-webpack-plugin supports some of these automated transformations. Its general usage is as follows:

// Using index.html as a template, generate a file called index.html to inject other dependencies into the appropriate place on the page
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    plugins: [
        new HtmlWebpackPlugin({
            filename: 'index.html'.template: path.resolve(__dirname, '.. /index.html'),
            favicon: 'src/common/assets/img/favicon.ico'.inject: true})],... };Copy the code

Okay, we’re ready to complete the webPack configuration. With the entry file, you also specify where the last packaged output goes, so there is an output option that specifies some configuration to build the output:

const config = require('./config');
module.exports = {
    entry: {
        app: './src/main.js'
    },
    output: {
        path: config.common.assetsRoot,
        publicPath: config.common.assetsPublicPath,
        filename: utils.genFilePathWithName('[name].js'),
        chunkFilename: utils.genFilePathWithName('[name].bundle.js')}};Copy the code

The output.filename and output.path attributes specify the name of the file that was eventually generated and where it was placed. The output.publicPath and output.chunkFilename attributes specify the reference path to the file.

module.exports = {
  / /...
  output: {
    publicPath: '/assets/'.chunkFilename: '[id].chunk.js'}};Copy the code

For a configuration like this, the final reference to the resource path might look like /assets/5.chunk.js, and then it would be inserted into an HTML file to look like this:

<script src="/assets/5.chunk.js"></script>
Copy the code

Filename refers to the filename of the main control logic (runtime dependency). The value [name]. Js means that the generated file should be app.js. ChunkFilename specifies the naming of other files to be loaded on demand. Utils genFilePathWithName method is implemented a will file according to file type classifies the logic.

/ * * *@file utils.js
 * @author nimingdexiaohai([email protected])
 */
const path = require('path');
const config = require('./config');
module.exports = {
    // Group files by suffix to the same type of file directory
    genFilePathWithName: function(fileName) {
        return path.posix.join(config.common.assetsSubDirectory, path.extname(fileName).substring(1), fileName); }};Copy the code

To put it more bluntly, you put all your JS files in your JS directory, and all your CSS files in your CSS directory. This makes for a simple categorization, and it can also be seen that output.filename supports a given path as a value. Path + output.filename, such as /assets/js/app.js.

/ * * *@file config.js
 * @author nimingdexiaohai([email protected])
 */
const path = require('path');
module.exports = {
    dev: {},prod: {},common: {
        assetsRoot: path.resolve(__dirname, '.. /dist'),
        assetsPublicPath: '/'.assetsSubDirectory: 'static'}};Copy the code

Some common configurations are extracted from config.js. Specified finished output configuration, and then look at the demand “deal with HTML/JS/VUE/CSS/LESS SASS/IMG/JPG documents”, the reason to fix the requirements, rather than in the dev – server according to the order, because the dev – server can only be with development time, We try to work out some common base configurations that can be reused in a variety of other scenarios, such as development, test, production, etc. The packaging of different files is implemented by various loaders, which is the great thing about WebPack, processing other types of files like JS files, building complete dependency diagrams. Its implementation requires the configuration of the moudle field.

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
    module: {
        rules: [{test: /\.vue$/,
                use: 'vue-loader'
            },
            {
                test: /\.js$/,
                use: [{
                    loader: 'babel-loader'.options: {
                        cacheDirectory: true}}].exclude: /node_modules/}, {test: /\.css$/,
                use: [MiniCssExtractPlugin.loader, 'css-loader'] {},test: /\.less$/,
                use: [MiniCssExtractPlugin.loader, 'css-loader'.'less-loader'] {},test: /\.(png|gif|svg|ico|jpe? g|woff2? |eot|ttf|otf)(\? . *)? $/,
                use: [{
                    loader: 'url-loader'.options: {
                        limit: 8192,}}]}]},};Copy the code

The Module field supports multiple items. Each item contains a re and loader configuration, which means that files that meet the re match are processed by the Loader. When the urL-loader is smaller than 8KB, the file will be converted to Base64 encoding and directly hardcoded into the output. When files are larger than 8kb, file-loader is used to process files. This is the implementation principle of url-loader. Therefore, you need to install file-loader by yourself. . Less file processing is relatively complicated, it specifies the three loader, loader is reverse loading in order according to the configuration, namely from before to after processing, in turn less processing for CSS first, CSS into the MiniCssExtractPlugin again. The loader. Mini-css-extract-plugin is a plug-in that extracts style files separately and packages them separately. To use the mini-css-extract-plugin, you need to specify the name of the extracted style file in the plugins, or you can use the default rule [name].css.

const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');

plugins: [
    new VueLoaderPlugin(),
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
        filename: utils.genFilePathWithName('[name].css')}),new HtmlWebpackPlugin({
        filename: 'index.html'.template: path.resolve(__dirname, '.. /index.html'),
        favicon: 'src/common/assets/img/favicon.ico'.inject: true})].Copy the code

The clean-webpack-plugin is used to clean up the last output directory before each package. The purpose of this method is to deal with the problem that when the file name contains hash, the file name will be different each time. If the file name is accumulated for a long time, there will be many useless discarded files. Therefore, clear the file directory before packaging, and only the current compiled output will be kept. At this point, we’re almost done with our common configuration. Don’t worry, there’s just one more thing. When the file directory is extremely long, or when we want to import files without suffixes, we can configure some aliases to implement a little lazy behavior.

resolve: { symlinks: false, modules: [path.resolve(__dirname, '../node_modules')], extensions: ['.js', '.vue', '.less', 'css'], alias: { 'vue': 'vue/dist/vue.esm.js', '@': path.resolve(__dirname, '.. /src') } },Copy the code

The resolve.symlinks field is used to specify the rule to resolve the soft chain. When enabled, symbolic link resources are resolved to their actual path, not their symbolic link location. Note that this may cause module parsing to fail when using the Symlink package’s tools, so it is not recommended to enable this configuration. Resolve. modules refers to where to continue searching if a direct match to your imported file path fails. For example, import vue form vue; Vue that matches the current directory definitely doesn’t have this file, so go to node_modules and you’ll find it. Most tripartite dependencies are resolved this way. The resolve.extensions are the ones you can omit when importing a file, and WebPack will try to find files that have those extensions in turn. Resolve. Alias Specifies an alias for some directories, usually to avoid writing lengthy paths.

At this point, basically all the basic configuration is complete. Let’s see what it looks like in full:

/ * * *@file webpack.config.base.js
 * @author nimingdexiaohai([email protected])
 */
const path = require('path');
const utils = require('./utils');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

const config = require('./config');

module.exports = {
    entry: {
        app: './src/main.js'
    },
    output: {
        path: config.common.assetsRoot,
        publicPath: config.common.assetsPublicPath,
        filename: utils.genFilePathWithName('[name].js'),
        chunkFilename: utils.genFilePathWithName('[name].bundle.js')},resolve: {
        symlinks: false.modules: [path.resolve(__dirname, '.. /node_modules')].extensions: ['.js'.'.vue'.'.less'.'css'].alias: {
            'vue': 'vue/dist/vue.esm.js'.The '@': path.resolve(__dirname, '.. /src')}},module: {
        rules: [{test: /\.vue$/,
                use: 'vue-loader'
            },
            {
                test: /\.js$/,
                use: [{
                    loader: 'babel-loader'.options: {
                        cacheDirectory: true}}].exclude: /node_modules/}, {test: /\.css$/,
                use: [MiniCssExtractPlugin.loader, 'css-loader'] {},test: /\.less$/,
                use: [MiniCssExtractPlugin.loader, 'css-loader'.'less-loader'] {},test: /\.(png|gif|svg|ico|jpe? g|woff2? |eot|ttf|otf)(\? . *)? $/,
                use: [{
                    loader: 'url-loader'.options: {
                        limit: 8192,}}]}]},plugins: [
        new VueLoaderPlugin(),
        new CleanWebpackPlugin(),
        new MiniCssExtractPlugin({
            filename: utils.genFilePathWithName('[name].css')}),new HtmlWebpackPlugin({
            filename: 'index.html'.template: path.resolve(__dirname, '.. /index.html'),
            favicon: 'src/common/assets/img/favicon.ico'.inject: true})].optimization: {
        splitChunks: {
            cacheGroups: {
                vendor: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'vendor'.chunks: 'initial'.priority: -10
                },
                default: {
                    minChunks: 2.priority: -20.reuseExistingChunk: true}}}}};Copy the code

【 summary 】

Doesn’t it look good? In the next section, we’ll look at the differential configuration of development and production environments. Stay tuned for how to Build your own scaffolding (PART 2)

Wechat update: