preface

Often when developing vUE projects, we use vuE-CLI scaffolding tools to build the project template we want. This way, we can write projects efficiently regardless of configuration. But vuE-CLI is similar to black box template configuration, for beginners, in fact, is very hurt. I spent a year or two writing business logic code and didn’t even know the basic configuration of WebPack.

Also, vuE-CLI, even if convenient, was not a perfect fit for our project, so we needed to manually build the initial project template. Lay a foundation for realizing the engineering of the project.

The project address

The project address

  • master: webpack4 + express + vue
  • ts-dev: webpack4 + express + vue(vue-class-component) + typescript(This article explains)

Build file directory

` - build build file directory | | - configs project configuration | | -- appEnvs. Js global variable configuration | | -- options. Js other configuration | | -- proxy. Js service proxy configuration | | - plugin plug-in | | | - rules rules ` -- development. | js development environment configuration ` -- production. | js production configuration ` -- webpack. Base. Js based environment configuration | ` -- - public public folder SRC code directory | - @ types typescript declaration file | | - | HTTP HTTP file - assets multimedia documents - components component files | | - store state file - utils tools File | - views folder view | | - home | | -- home. The module. The module SCSS CSS file | | - index. The TSX TSX file | -- App. TSX | -- main. Ts entry documents | - router. Ts | - routing file. Editorconfig. | - prettierrc. | - postcssrc. Js | -- Babel. Config. Js | -- package. Json | - tsconfig.json |-- tslint.jsonCopy the code

Webpack configuration

  • webpack.base.js
const path = require("path");
// Throw some configuration, such as port, builtPath
const config = require("./configs/options");
// CSS less SCSS loder integration
const cssLoaders = require("./rules/cssLoaders");

function resolve(name) {
  return path.resolve(__dirname, "..", name);
}

// Development environment changes do not refresh the page, hot replacement
function addDevClient(options) {
  if (options.mode === "development") {
    Object.keys(options.entry).forEach(name= > {
      options.entry[name] = [
        "webpack-hot-middleware/client? reload=true&noInfo=true"
      ].concat(options.entry[name]);
    });
  }
  return options.entry;
}

/ / webpack configuration
module.exports = options= > {
  const entry = addDevClient({
    entry: {
      app: [resolve("src/main.ts")]},mode: options.mode
  });
  return {
    // Webpack pack entry
    entry: entry,
    // Define options for how webpack outputs
    output: {
      publicPath: "/".// The output directory of the build file
      path: resolve(config.builtPath || "dist"), // The destination path of all output files
      filename: "static/js/[name].[hash].js".// Entry Chunk file naming template
      chunkFilename: "static/js/[name].[chunkhash].js" // Name of a non-entry chunk file
    },
    resolve: {
      // The module's lookup directory
      modules: [resolve("node_modules"), resolve("src")].// Extension of the file used
      extensions: [".tsx".".ts".".js".".vue".".json"].// List of module aliases
      alias: {
        vue$: "vue/dist/vue.esm.js"."@components": resolve("src/components"),
        "@": resolve("src")}},// Prevent some import packages from being packaged into bundles,
    // Get these external dependencies at runtime.
    // Reduce the packing volume. Add CDN import on the home page
    externals: {
      vue: "Vue".vuex: "Vuex"."vue-router": "VueRouter"
    },
    // Module configuration
    module: {
      rules: [{test: /(\.jsx|\.js)$/.use: ["babel-loader"].exclude: /node_modules/
        },
        //.tsx file parsing
        {
          test: /(\.tsx)$/.exclude: /node_modules/.use: ["babel-loader"."vue-jsx-hot-loader"."ts-loader"] {},test: /(\.ts)$/.exclude: /node_modules/.use: ["babel-loader"."ts-loader"]},... cssLoaders({mode: options.mode,
          sourceMap: options.sourceMap,
          extract: options.mode === "production"{}),test: /\.(png|jpe? g|gif|svg)(\? . *)? $/.loader: "url-loader".options: {
            limit: 10000.name: "static/img/[name].[hash:7].[ext]"}}, {test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\? . *)? $/.loader: "url-loader".options: {
            limit: 10000.name: "static/media/[name].[hash:7].[ext]".fallback: "file-loader"}}, {test: /\.(woff2? |eot|ttf|otf)(\? . *)? $/.loader: "url-loader".options: {
            limit: 10000.name: "static/fonts/[name].[hash:7].[ext]"}}]}}; };Copy the code

Development Environment Configuration

  • development.js
const webpack = require('webpack')
const path = require('path')

const express = require('express')

const merge = require('webpack-merge')
const chalk = require('chalk')

// Implement local service hot replacement
/ / https://github.com/webpack-contrib/webpack-hot-middleware
const webpackMiddleware = require('webpack-dev-middleware')
const webpackHotMiddleware = require('webpack-hot-middleware')

const ProgressBarPlugin = require("progress-bar-webpack-plugin");
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')

const baseConfig = require('./webpack.base')
const config = require('./configs/options')

const HtmlWebpackPlugin = require('html-webpack-plugin')

const proxyTable = require('./configs/proxy')

// http-proxy-middleware adds a proxy
const useExpressProxy = require('./plugins/useExpressProxy')

// Global variables
const appEnvs = require('./configs/appEnvs')
const app = express()

// Merge webpack requests
const compiler = webpack(merge(baseConfig({ mode: 'development'{}),mode: 'development'.devtool: '#cheap-module-eval-source-map'./ / the plugin
  plugins: [
    new ProgressBarPlugin(), // Progress bar plug-in
    new FriendlyErrorsWebpackPlugin(),
    // Set a shortcut to the process.env environment variable via DefinePlugin.
    new webpack.EnvironmentPlugin(appEnvs),
    // Module hotreplace plugin, used with Webpack-hot-middleware
    new webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      template: resolve('./public/index.html'),
      filename: 'index.html'})].optimization: {
    // Skip the build phase without exiting because of an error code
    noEmitOnErrors: true}}))function resolve (name) {
  return path.resolve(__dirname, '.. ', name)
}

const devMiddleware = webpackMiddleware(compiler, {
  / / with webpack publicPath
  publicPath: '/'.logLevel: 'silent'
})

const hotMiddleware = webpackHotMiddleware(compiler, {
  log: false
})

compiler.hooks.compilation.tap('html-webpack-plugin-after-emit', () => {
  hotMiddleware.publish({
    action: 'reload'})})// Load middleware
app.use(devMiddleware)
app.use(hotMiddleware)

// Add the proxy configuration
useExpressProxy(app, proxyTable)

devMiddleware.waitUntilValid((a)= > {
  console.log(chalk.yellow(`I am ready. open http://localhost:${ config.port || 3000 } to see me.`))
})

app.listen(config.port || 3000)

Copy the code

Production Environment Configuration

  • production.js
const webpack = require('webpack')
const path = require('path')

const ora = require('ora')
const chalk = require('chalk')
const merge = require('webpack-merge')

const baseConfig = require('./webpack.base.js')

// Replace extract-text-webpack-plugin for extracting CSS files
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// For optimized compression of CSS files
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')

const HtmlWebpackPlugin = require('html-webpack-plugin')
// Clear the dist file when rebuilding
const CleanWebpackPlugin = require('clean-webpack-plugin')

const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')

const config = require('./configs/options')
const appEnvs = require('./configs/appEnvs')

const compiler = webpack(merge(baseConfig({ mode: 'production'{}),mode: 'production'.output: {
    publicPath: '/'
  },
  performance: {
    hints: false
  },
  plugins: [
    new webpack.HashedModuleIdsPlugin(),
    new webpack.EnvironmentPlugin(appEnvs),
    new webpack.SourceMapDevToolPlugin({
      test: /\.js$/.filename: 'sourcemap/[name].[chunkhash].map'.append: false
    }),
    new CleanWebpackPlugin([`${config.builtPath || 'dist'}/ * `] and {root: path.resolve(__dirname, '.. ')}),new HtmlWebpackPlugin({
      template: resolve('./public/index.html'),
      filename: 'index.html'.chunks: ['app'.'vendors'.'mainifest'].minify: {
        removeComments: true.collapseWhitespace: true.removeAttributeQuotes: true}}),new MiniCssExtractPlugin({
      filename: 'static/css/[name].[contenthash].css'
      chunkFilename: 'static/css/[id].[contenthash].css'})].optimization: {
    // Package the WebPack runtime generation code into mainifest
    runtimeChunk: {
      name: 'mainifest'
    },
    // Replace commonChunkPlugin to split code
    splitChunks: {
      chunks: 'async'.minSize: 30000.minChunks: 1.maxAsyncRequests: 5.maxInitialRequests: 3.automaticNameDelimiter: '~'.name: true.cacheGroups: {
        // node_modules are merged into vendor.js
        vendor: {
          test: /node_modules\/(.*)\.js/.name: 'vendors'.chunks: 'initial'.priority: - 10.reuseExistingChunk: false
        },
        // Combine CSS into a single file with the mini-CSs-extract-plugin
        styles: {
          name: 'styles'.test:  /(\.less|\.scss|\.css)$/.chunks: 'all'.enforce: true,}}},minimizer: [
      ParallelUglifyPlugin enables serial compression of JS files to enable parallel execution of multiple sub-processes
      new ParallelUglifyPlugin({
        uglifyJS: {
          output: {
            beautify: false.comments: false
          },
          compress: {
            warnings: false.drop_console: true.collapse_vars: true.reduce_vars: true}},cache: true.// Enable caching
        parallel: true.// Parallel compression
        sourceMap: true // set to true if you want JS source maps
      }),
      / / compress CSS
      new OptimizeCssAssetsPlugin({
        assetNameRegExp: /(\.less|\.scss|\.css)$/g.cssProcessor: require("cssnano"), // CSS compression optimizer
          cssProcessorOptions: {
            safe: true.autoprefixer: { disable: true },
            discardComments: { removeAll: true}},// Remove all comments
        canPrint: true]}}})))function resolve (name) {
  return path.resolve(__dirname, '.. ', name)
}

const spinner = ora('building for production... ').start()

compiler.run((err, stats) = > {
  spinner.stop()
  if (err) throw err
  process.stdout.write(stats.toString({
    colors: true.modules: false.children: false.chunks: false.chunkModules: false
  }) + '\n\n')

  console.log(chalk.cyan('  Build complete..\n'))
  console.log(chalk.yellow(
    ' Tip: built files are meant to be served over an HTTP server.\n' +
    ' Opening index.html over file:// won\'t work.\n'))})Copy the code

Vue + TypeScript

To use typescript in VUE, we use vue-class-Component. First we need to make the project JSX and typescript compatible

Babel 7

I used babel@7 with some changes compared to 6. All packages are @babel/ XXX

For JSX compatibility, I just used @vue/babel-preset- JSX and loaded babel-plugin-transform-jsx internally

  • babel.config.js
module.exports = {
  presets: [['@babel/preset-env',
      {
        modules: false.targets: {
          browsers: ['> 1%'.'last 2 versions'.'not ie <= 8']}}],'@vue/babel-preset-jsx'].plugins: [
    '@babel/plugin-transform-runtime'].comments: false.env: {
    test: {
      presets: ['@babel/preset-env'].plugins: ['babel-plugin-dynamic-import-node']}}}Copy the code

TsConfig

  • tsconfig.js
{
  "include": [
      "src/**/*.ts"."src/**/*.tsx"."src/**/*.vue"."tests/**/*.ts"."tests/**/*.tsx"]."exclude": ["node_modules"]."compilerOptions": {
      // typeRoots option has been previously configured
      "typeRoots": [
          // add path to @types
          "src/@types"]."baseUrl": "."."paths": {
          "*": ["types/*"]."@ / *": ["src/*"]},// Parse in strict mode
      "strict": true.// Support JSX in.tsx files
      "jsx": "preserve".// Use the JSX factory function
      "jsxFactory": "h".// Allow default imports from modules that do not have default exports set
      "allowSyntheticDefaultImports": true.// Enable the decorator
      "experimentalDecorators": true.// "strictFunctionTypes": false,
      // Allows javascript files to be compiled
      "allowJs": true.// The module system used
      "module": "esnext".// Compile the output target ES version
      "target": "es5".// How to handle modules
      "moduleResolution": "node".// There is an error with an implied any type on expressions and declarations
      "noImplicitAny": true."importHelpers": true."lib": ["dom"."es5"."es6"."es7"."es2015.promise"]."sourceMap": true."pretty": true."esModuleInterop": true}}Copy the code
  • tslint.js
{
  "defaultSeverity": "warning"."extends": ["tslint:recommended"]."linterOptions": {
    "exclude": ["node_modules/**"]},"allowJs": true."rules": {
    "arrow-parens": false."trailing-comma": false."quotemark": [true]."indent": [true."spaces".2]."interface-name": false."ordered-imports": false."object-literal-sort-keys": false."no-console": false."no-debugger": false."no-unused-expression": [true."allow-fast-null-checks"]."no-unused-variable": false."triple-equals": true."no-parameter-reassignment": true."no-conditional-assignment": true."no-construct": true."no-duplicate-super": true."no-duplicate-switch-case": true."no-object-literal-type-assertion": true."no-return-await": true."no-sparse-arrays": true."no-string-throw": true."no-switch-case-fall-through": true."prefer-object-spread": true."radix": true."cyclomatic-complexity": [true.20]."member-access": false."deprecation": false."use-isnan": true."no-duplicate-imports": true."no-mergeable-namespace": true."encoding": true."import-spacing": true."interface-over-type-literal": true."new-parens": true."no-angle-bracket-type-assertion": true."no-consecutive-blank-lines": [true.3]}}Copy the code

Project code

With everything in place, we started writing.tsx files

  • App.tsx
import { Vue, Component } from "vue-property-decorator";
import { CreateElement } from "vue";

@Component
export default class extends Vue {
  // CreateElement (h: CreateElement)
  // There is no automatic h injection, there is a problem with babel-plugin-transform-jsx
  // Why is there no auto-injection
  render(h: CreateElement) {
    return (
      <div id="app">
        <router-view />
      </div>); }}Copy the code

At the end

The article is not very detailed, in fact, many important project configurations have not been added, Examples include Commitizen, Lint-stage, Jest, Cypress, babel-plugin-vue-jsx-sync, babel-plugin-jsx-v-model…..

It is also about understanding the whole project construction process from scratch.

Code uploaded. Welcome. The project address