29

前言

vue3.0已经出了一段时间了,网上也有大量的实践教程可供参考。但从实际出发,由于开发的chrome插件需要升级到vue3.0,同时准备换上ant-design-vue,由于没有使用vue-cli脚手架,在这个升级过程中还是踩了不少坑,于是将自己搭建整个项目的过程记录下来,如对大家有帮助,避免少踩坑也是极好的。(没有vue-router是因为chrome插件用不上)

该文章记录日期为2020年11月23日,但项目实际搭建日期应该是在一周前左右。由于目前vue3.0刚出不久,包括其配套生态也还处于beta版本或者刚出RC版,后续可能出现一些变动,该实践仅作为目前版本的参考。

顺带一提,若想用上vue3.0下比较成熟的UI框架,目前除了ant-design-vue和vant并没有太多选择,如Vuetify,Quasar等优秀的UI框架都还没适配vue3.0,可以耐心等一等。

一、webpack5基础环境配置

1.在项目下新建一个package.json配置文件

npm init -y

2.安装webpack5,webpack-cli和webpack-dev-server

说明一下,就在4天前和19天前,webpack和webpack-cli又更新了版本,webpack为5.6.0, webpack-cli为4.2.0。而我搭建时的版本为webpack 5.4.0
npm i webpack webpack-cli webpack-dev-server -D

这里出现第一个坑,webpack-cli若为4.2.0版本,使用webpack-dev-server会报错Error: Cannot find module 'webpack-cli/bin/config-yargs',由于版本之间不兼容造成。
解决方式,暂时将webpack-cli版本降为3.3.12

3.安装所需依赖

Less和CSS
由于我们使用了ant-design, CSS预编译就用less。
npm i less less-loader css-loader style-loader -D
TypeScript
npm i typescript ts-loader -D
Babel
npm i @babel/core @babel/preset-env @babel/plugin-transform-runtime babel-loader babel-plugin-import -D
npm i @babel/runtime -S
Vue和Vuex(指定版本)
npm i vue@next vuex@4.0.0-rc.1

这里遇见第二个坑,之前的vue-template-compiler模板编译在这个版本会报错,改用@vue/compiler-sfc

npm i vue-loader@16.0.0-rc.1 @vue/compiler-sfc -D
Ant-design-vue(需指定版本)
npm i ant-design-vue@2.0.0-beta.15 -S
相关webpack插件
npm i clean-webpack-plugin copy-webpack-plugin html-webpack-plugin mini-css-extract-plugin -D

二、新建项目文件

文件目录
--index.html
--src
    --main.ts
    --shims-vue.d.ts
    --pages
        --App.vue
    --store
        --Actions.ts
        --Getters.ts
        --Mutations.ts
        --State.ts
        --index.ts
    --assets
        --imgs
            --xxx.png
--tsconfig.json
--webpack.dev.config.js
--webpack.prod.config.js
--package.json
index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app"></div>
</body>

</html>
main.ts
import { createApp } from "vue";
import box from "./pages/App.vue";
import store from './store';
import Button from 'ant-design-vue/lib/Button';
import 'ant-design-vue/dist/antd.less';

const app = createApp(box);
app.use(store).use(Button).mount('#app');
store/index.ts
import { createStore } from 'vuex';
import state from './State';
import mutations from './Mutations';
import actions from './Actions';
import getters from './Getters';

const store = createStore({
    state,
    mutations,
    actions,
    getters
})

export default store;
store/Mutations.ts
interface mutationObj {
    addCount: Function,
    [propName: string]: any
}

const mutations: mutationObj = {
    addCount(state) {
        state.counter += 1;
    }
}

export default mutations;
store/State.ts
interface stateObj {
    counter: number,
    [propName: string]: any
}

const state: stateObj = {
    counter: 0
}

export default state;
App.vue
<template>
    <div class="box">
        {{ counter }}
        <a-button type="primary" @click="addCount">添加</a-button>
    </div>
</template>

<script lang="ts">
import { computed } from "vue";
import { useStore } from "vuex";
export default {
    name: "App",
    // 组合API函数入口
    setup() {
        const store: any = useStore();

        const counter = computed(() => {
            return store.state.counter;
        });

        const addCount = () => {
            store.commit("addCount");
        };

        return { counter, addCount };
    },
};
</script>

<style lang="less" scoped>
.box {
    width: 300px;
    height: 300px;
    background: red;
}
</style>

三、配置文件

webpack.dev.config.js
const path = require('path');
const { HotModuleReplacementPlugin, IgnorePlugin } = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader');

const config = {
    mode: 'development',
    entry: './src/main.ts',
    output: {
        filename: 'index.js',
        path: path.resolve(__dirname, 'dist')
    },
    devServer: {
        contentBase: false,
        // publicPath: './dist',
        hot: true,
        port: 8080,
        open: true,
        // hotOnly: true,
        compress: true,
        overlay: true
    },
    watchOptions: {
        ignored: /node_modules/
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            title: 'Hot Module Replacement',
            template: 'index.html'
        }),
        new IgnorePlugin(/^\.\/locale$/, /moment$/),
        new HotModuleReplacementPlugin(),
        new VueLoaderPlugin()
    ],
    module: {
        rules: [
            // babel使用runtime,避免将不需要的代码注入
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: [{
                    loader: 'babel-loader',
                    options: {
                        // cacheDirectory: true,
                        presets: ['@babel/preset-env'],
                        plugins: [
                            '@babel/plugin-transform-runtime',
                            ['import', {
                                "libraryName": "antd",
                                "style": true,   // or 'css'
                            }, 'antd']
                        ]
                    }
                }],
            },
            {
                test: /\.ts$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'ts-loader',
                        options: {
                            // 指定特定的ts编译配置,为了区分脚本的ts配置
                            configFile: path.resolve(__dirname, './tsconfig.json'),
                            appendTsSuffixTo: [/\.vue$/]
                        }
                    }
                ]
            },
            {
                test: /\.vue$/,
                loader: 'vue-loader'
            },
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.less$/,
                use: ['style-loader', 'css-loader',
                    {
                        loader: 'less-loader',
                        options: {
                            lessOptions: {
                                modifyVars: {
                                    'primary-color': '#4608e2',
                                    'link-color': '#4608e2',
                                    'border-radius-base': '20px',
                                },
                                javascriptEnabled: true,
                            }
                        }
                    }],
            }
        ]
    },
    resolve: {
        extensions: ['.js', '.ts']
    },
};


module.exports = (env) => {
    console.log(`当前执行${env.mode}模式`);
    return config;
}
webpack.prod.config.js
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { VueLoaderPlugin } = require('vue-loader');

const config = {
    mode: 'production',
    entry: './src/main.ts',
    output: {
        filename: 'index.js',
        path: path.resolve(__dirname, 'dist')
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            title: 'Hot Module Replacement',
            template: 'index.html'
        }),
        new CopyWebpackPlugin({
            patterns: [
                {
                    from: 'src/assets',
                    to: 'assets'
                },
            ]
        }),
        new MiniCssExtractPlugin({
            filename: './index.css'
        }),
        new VueLoaderPlugin()
    ],
    module: {
        rules: [
            // babel使用runtime,避免将不需要的代码注入
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: [{
                    loader: 'babel-loader',
                    options: {
                        cacheDirectory: true,
                        presets: ['@babel/preset-env'],
                        plugins: [
                            ['import', {
                                "libraryName": "antd",
                                "style": true,   // or 'css'
                            }, 'antd']
                        ]
                    }
                }],
            },
            {
                test: /\.ts$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'ts-loader',
                        options: {
                            // 指定特定的ts编译配置,为了区分脚本的ts配置
                            configFile: path.resolve(__dirname, './tsconfig.json'),
                            appendTsSuffixTo: [/\.vue$/]
                        }
                    }
                ]
            },
            {
                test: /\.vue$/,
                use: ['vue-loader']
            },
            {
                test: /\.css$/,
                use: [MiniCssExtractPlugin.loader, 'css-loader']
            },
            {
                test: /\.less$/,
                use: [MiniCssExtractPlugin.loader, 'css-loader', {
                    loader: 'less-loader',
                    options: {
                        lessOptions: {
                            modifyVars: {
                                'primary-color': '#4608e2',
                                'link-color': '#4608e2',
                                'border-radius-base': '20px',
                            },
                            javascriptEnabled: true,
                        }
                    }
                }]
            }
        ]
    },
    resolve: {
        extensions: ['.js', '.ts']
    },
};

module.exports = (env) => {
    console.log(`当前执行${env.mode}模式`);
    return config;
}
tsconfig.json
{
    "compilerOptions": {
        // "esModuleInterop": true,
        "incremental": true, // 增量编译
        "allowJs": true,
        "lib": [
            "es6",
            "dom",
            "es2017"
        ],
        "types": []
    },
    "exclude": [
        "node_modules"
    ]
}
shims-vue.d.ts
declare module '*.vue' {
    import Vue from 'vue';
    export default Vue;
}

declare module "vue/types/vue" {
    interface Vue {
        $http: any;
        $Message: any;
        $Modal: any;
    }
}
package.json
{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server --config webpack.dev.config.js --env.mode development --watch --profile",
    "build": "webpack --config webpack.prod.config.js --env.mode production"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.12.9",
    "@babel/plugin-transform-runtime": "^7.12.1",
    "@babel/preset-env": "^7.12.7",
    "@vue/compiler-sfc": "^3.0.2",
    "babel-loader": "^8.2.1",
    "babel-plugin-import": "^1.13.3",
    "clean-webpack-plugin": "^3.0.0",
    "copy-webpack-plugin": "^6.3.2",
    "css-loader": "^5.0.1",
    "html-webpack-plugin": "^4.5.0",
    "less": "^3.12.2",
    "less-loader": "^7.1.0",
    "mini-css-extract-plugin": "^1.3.1",
    "style-loader": "^2.0.0",
    "ts-loader": "^8.0.11",
    "typescript": "^4.1.2",
    "vue-loader": "^16.0.0-rc.1",
    "webpack": "^5.6.0",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0"
  },
  "dependencies": {
    "@babel/runtime": "^7.12.5",
    "ant-design-vue": "^2.0.0-beta.15",
    "vue": "^3.0.2",
    "vuex": "^4.0.0-rc.1"
  }
}

四、命令执行

开发环境
npm start
生产环境
npm run build

五、最终效果

image.png

六、结语

这个项目搭建的案例,只做了最简单的示例,就是利用vuex做一个计数器。其实chrome插件会有多个入口文件,最终打包出来的文件结构和开发时还是有所区别,后面会单独写篇文章介绍。

我将该示例放在了github上,欢迎食用~
https://github.com/gxy5202/we...


Gomi
2.4k 声望2.3k 粉丝

前端,业余PPT设计师