不用Cli脚手架搭建Webpack5配置Vue3项目

1,521 阅读5分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

前言

相信大家都会使用cli框架搭建快速搭建自己的项目,这样做快速又方便。小凌记得自己第一家公司的架构师告诉小凌,一个好的框架就像是一件艺术品。他所搭建的项目都是从头开始搭建的。小凌今天也来带代价从头开始搭建一个Webpack 5 + Vue 3 + TypeScript + Pinia + VueRouter项目。

package.json

首先我们初始化项目。我们在项目根目录运行一下命令

npm init -y

运行后项目中就有一个package.json

01.png

package.json是项目描述文件,记录了当前项目信息,例如项目名称、版本、作者、github地址、 当前项目依赖了哪些第三方模块等。

插入运行命令,方便我们直接运行项目。

"scripts": {
    "serve": "webpack serve --progress",
    "build": "webpack --env production --progress",
    "lint": "eslint --ext ts,tsx,js,jsx src && vue-tsc --noEmit"
  },

写入devDependencies

devDependencies是只会在开发环境下依赖的模块,生产环境不会被打入包内。通过NODE_ENV=developementNODE_ENV=production指定开发还是生产环境。 

"devDependencies": {
    "@babel/core": "^7.16.10",
    "@types/node": "^16.0.0",
    "@typescript-eslint/eslint-plugin": "^5.10.0",
    "@typescript-eslint/parser": "^5.10.0",
    "@vue/babel-plugin-jsx": "^1.1.1",
    "@vue/compiler-sfc": "3.2.28",
    "autoprefixer": "^10.4.2",
    "babel-loader": "^8.2.3",
    "copy-webpack-plugin": "^10.2.1",
    "css-loader": "^6.5.1",
    "css-minimizer-webpack-plugin": "^3.4.1",
    "eslint": "^8.7.0",
    "eslint-config-standard-with-typescript": "^21.0.1",
    "eslint-plugin-import": "^2.25.4",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-promise": "^6.0.0",
    "fork-ts-checker-webpack-plugin": "^6.5.0",
    "html-webpack-plugin": "^5.5.0",
    "mini-css-extract-plugin": "^2.5.2",
    "pinia": "^2.0.9",
    "postcss-loader": "^6.2.1",
    "sass": "^1.49.0",
    "sass-loader": "^12.4.0",
    "style-loader": "^3.3.1",
    "terser-webpack-plugin": "^5.1.4",
    "ts-loader": "^9.2.6",
    "ts-node": "^10.4.0",
    "tslib": "^2.3.1",
    "typescript": "~4.5.5",
    "vue": "3.2.28",
    "vue-loader": "^17.0.0",
    "vue-router": "^4.0.12",
    "vue-tsc": "^0.31.1",
    "webpack": "^5.67.0",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.7.3"
  }

其中,devDependencies用于本地环境开发时候。dependencies用户发布环境。

webpack配置

在根目录新建webpack.config.ts

02.png

在头部引入需要的引用

import * as path from 'path'
import * as webpack from 'webpack'
import * as HtmlWebpackPlugin from 'html-webpack-plugin'
import * as CopyWebpackPlugin from 'copy-webpack-plugin'
import { VueLoaderPlugin } from 'vue-loader'
import * as MiniCssExtractPlugin from 'mini-css-extract-plugin'
import * as TerserWebpackPlugin from 'terser-webpack-plugin'
import * as CssMinimizerWebpackPlugin from 'css-minimizer-webpack-plugin'
import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')

配置webpack的输入和输出

export default function (env: Env): webpack.Configuration {
  const isProduction = env.production;
  const mode = env.production ? "production" : "development";
  const context = __dirname;
  const webpackConfig: webpack.Configuration = {
    mode,
    devtool: isProduction ? false : "eval-source-map",
    context: context,
    target: "web",
    entry: {
      app: [path.join(context, "src/index")],
    },
    output: {
      filename: "[name].js",
      path: path.join(context, "dist"),
      environment: {
        arrowFunction: false,
        bigIntLiteral: false,
        const: false,
        destructuring: false,
        dynamicImport: false,
        forOf: false,
        module: false,
      },
      publicPath: isProduction ? "" : "/",
    },
    node: false,
    stats: {
      colors: true,
      children: false,
      modules: false,
      entrypoints: false,
    }
  return webpackConfig;
}

配置处理JS & JSX.

 module: {
      rules: [
        {
          test: /\.(m|c)?jsx?$/,
          exclude: /node_modules/,
          use: [{ loader: require.resolve("babel-loader") }],
        },
        {
          test: /\.ts$/,
          exclude: /node_modules/,
          use: [tsLoader(context)],
        },
        {
          test: /\.tsx$/,
          exclude: /node_modules/,
          use: [{ loader: require.resolve("babel-loader") }, tsLoader(context)],
        }]
  }
  
function tsLoader(context: string): webpack.RuleSetUseItem {
  return {
    loader: require.resolve("ts-loader"),
    options: {
      appendTsSuffixTo: [/\.vue$/],
      transpileOnly: true,
      configFile: path.join(context, "tsconfig.json"),
    },
  };
},
plugins: [
      new ForkTsCheckerWebpackPlugin({
        async: !isProduction,
        typescript: {
          memoryLimit: 4096,
          configFile: path.join(context, "tsconfig.json"),
        },
      }),
 ]


配置处理CSS & SCSS。


function cssLoader(importLoaders = 0): webpack.RuleSetUseItem {
  return {
    loader: require.resolve("css-loader"),
    options: {
      modules: {
        auto: true,
        localIdentName: "[path][name]__[local]",
      },
      importLoaders,
    },
  };
}

module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            isProduction
              ? { loader: MiniCssExtractPlugin.loader }
              : { loader: require.resolve("style-loader") },
            cssLoader(1),
            { loader: require.resolve("postcss-loader") },
          ],
        },
        {
          test: /\.s[ac]ss$/i,
          use: [
            isProduction
              ? { loader: MiniCssExtractPlugin.loader }
              : { loader: require.resolve("style-loader") },
            cssLoader(2),
            { loader: require.resolve("postcss-loader") },
            { loader: require.resolve("sass-loader") },
          ],
        },
     ]
     }

Vue相关配置。

module: {
      rules: [
        {
          test: /\.vue$/,
          use: [{ loader: require.resolve("vue-loader") }],
        },
      ]
 }
 
plugins: [
      new webpack.DefinePlugin({
            __VUE_OPTIONS_API__: "false", // 不使用Vue 2的选项式API
            __VUE_PROD_DEVTOOLS__: "false", // 生产环境不需要devtools 支持
      }),
      new VueLoaderPlugin(),
]

处理各种资源文件的配置。

module: {
      rules: [
            {
              test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
              type: "asset",
              generator: {
                filename: "img/[name].[ext]",
              },
            },
            {
              test: /\.(svg)(\?.*)?$/,
              type: "asset/resource",
              generator: {
                filename: "img/[name].[ext]",
              },
            },
            {
              test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
              type: "asset",
              generator: {
                filename: "media/[name].[ext]",
              },
            },
            {
              test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
              type: "asset",
              generator: {
                filename: "fonts/[name].[ext]",
              },
            },
        ]
}

模块解析快捷方式

resolve: {
      alias: {
        "@": path.join(context, "src"),
      },
      extensions: [
        ".tsx",
        ".ts",
        ".mjs",
        ".cjs",
        ".js",
        ".jsx",
        ".vue",
        ".scss",
        ".sass",
        ".css",
        ".json",
      ],
}

HTML和静态文件


plugins: [
      new HtmlWebpackPlugin({
        title: "vue3-webpack",
        template: path.join(context, "public/index.html"),
        filename: "index.html",
        minify: isProduction
          ? {
              removeComments: true,
              collapseWhitespace: true,
              removeAttributeQuotes: true,
              collapseBooleanAttributes: true,
              removeScriptTypeAttributes: true,
            }
          : false,
        cache: false,
      }),
      new CopyWebpackPlugin({
        patterns: [
          {
            from: path.join(context, "public"),
            to: path.join(context, "dist"),
            toType: "dir",
            globOptions: {
              ignore: [
                "**/.gitkeep",
                "**/.DS_Store",
                path.join(context, "public/index.html").replace(/\\/g, "/"),
              ],
            },
            noErrorOnMissing: true,
          },
        ],
      })
]

生产抽取压缩CSS,开发配置WDS

if (isProduction) {
    webpackConfig.plugins!.push(
      new MiniCssExtractPlugin({
        filename: "[name].css",
      })
    );
    webpackConfig.optimization = {
      minimizer: [
        new TerserWebpackPlugin({
          parallel: true,
          extractComments: false,
          terserOptions: {
            ecma: 2018 as 2018,
            output: {
              comments: false,
              beautify: false,
            },
          },
        }),
        new CssMinimizerWebpackPlugin({
          minimizerOptions: {
            preset: [
              "default",
              {
                mergeLonghand: false,
                cssDeclarationSorter: false,
              },
            ],
          },
        }),
      ],
    };
  } else {
    webpackConfig.devServer = {
      host: "0.0.0.0",
      port: 8090,
      open: false,
      static: path.join(context, "dist"),
      devMiddleware: {
        publicPath: "/",
      },
      proxy: {},
    };
  }

Typesctipt配置

新建tsconfig.json用于配置Typesctipt

03.png

tsconfig.json

{
  "compilerOptions": {
    "jsx": "preserve",
    "module": "esnext",
    "target": "es5",
    "importHelpers": true,
    "noEmitHelpers": true,
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitAny": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "noImplicitOverride": true,
    "sourceMap": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["./src/**/*"],
  "ts-node": {
    "compilerOptions": {
      "module": "CommonJS",
      "target": "es2019"
    }
  }
}

不懂Typescript?可以参考我这篇文章《一篇文章带你了解Typescript那些事儿》

babel配置

Babel是将ES6及以上版本的代码转换为ES5的工具。

在根目录下新建babel.config.js

04.png

babel.config.js

module.exports = {
  plugins: ["@vue/babel-plugin-jsx"],
};

postcss配置

1、作用 PostCSS 就是 CSS 界的 Babel,承担css处理器的角色。

2、到底做了什么 1)把源代码(或者符合一定条件的扩展语法)解析为一个自带遍历访问、节点操作接口的树; 2)把语法树输出为代码字符串。

在根目录下新建postcss.config.js

module.exports = {
  plugins: [
    require('autoprefixer')
  ]
}

配置eslint检查

eslint作用: 1.审查代码是否符合编码规范和统一的代码风格; 2.审查代码是否存在语法错误;

在根目录下新建.eslintrc.js

.eslintrc.js

module.exports = {
  root: true,
  env: {
    node: true,
    browser: true
  },
  parser: '@typescript-eslint/parser',
  extends: [
    'standard-with-typescript'
  ],
  rules: {
    '@typescript-eslint/no-unused-vars': 'error',
    '@typescript-eslint/no-namespace': 'off',
    '@typescript-eslint/strict-boolean-expressions': 'off',
    '@typescript-eslint/promise-function-async': 'off',
    '@typescript-eslint/no-non-null-assertion': 'off',
    '@typescript-eslint/member-delimiter-style': ['error', {
      multiline: {
        delimiter: 'none',
        requireLast: true
      },
      singleline: {
        delimiter: 'semi',
        requireLast: false
      }
    }]
  },
  globals: {},
  parserOptions: {
    ecmaVersion: 2020,
    sourceType: 'module',
    project: './tsconfig.json',
    tsconfigRootDir: __dirname,
    createDefaultProgram: true
  }
}

src下的配置

Vue SFC TS声明

在src文件夹下新建 shims-vue.d.ts

image.png

shims-vue.d.ts

declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

index.ts

在src文件夹下新建 index.ts

05.png

index.ts

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import router from '@/routers/index'
import App from './App'
const app = createApp(App)
app.use(createPinia())
app.use(router).mount('#app')

页面新增

接下来我们就可以配置相关的页面了

06.png

设置router和store

07.png

router/index.js

import { createRouter, createWebHashHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
import type { Component } from 'vue'
const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'home',
    redirect: { name: 'home' }
  },
 
]
const router = createRouter({
  history: createWebHashHistory(),
  routes
})
export default router

stores/index.js

import { defineStore } from 'pinia'
export const useMainStore = defineStore('main', {
  state: () => ({
    count: 0
  }),
  getters: {
    computedCount (state) {
      return state.count * 2
    }
  },
  actions: {
    add (value: number) {
      this.count += value
    },
    sub (value: number) {
      this.count -= value
    }
  }
})

到此我们的项目就配置完成了。

运行

执行 npm installnpm run

08.png

最后要说的

以上代码项目地址:项目地址

大家有什么问题欢迎评论哦~如果你觉得小凌写的不错那么请给小凌一个赞呗。你的支持是我前进下去的动力🙂