「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。
前言
相信大家都会使用cli框架搭建快速搭建自己的项目,这样做快速又方便。小凌记得自己第一家公司的架构师告诉小凌,一个好的框架就像是一件艺术品。他所搭建的项目都是从头开始搭建的。小凌今天也来带代价从头开始搭建一个Webpack 5 + Vue 3 + TypeScript + Pinia + VueRouter项目。
package.json
首先我们初始化项目。我们在项目根目录运行一下命令
npm init -y
运行后项目中就有一个package.json
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=developement或NODE_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。
在头部引入需要的引用。
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。
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
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
shims-vue.d.ts
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
index.ts
在src文件夹下新建 index.ts
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')
页面新增
接下来我们就可以配置相关的页面了
设置router和store
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 install、npm run
最后要说的
以上代码项目地址:项目地址
大家有什么问题欢迎评论哦~如果你觉得小凌写的不错那么请给小凌一个赞呗。你的支持是我前进下去的动力🙂