├── .browserslistrc
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
└── index.html
├── src
├── App.tsx
├── a.png
├── b.jpg
├── main.ts
├── shims-vue.d.ts
└── typings
│ └── images.d.ts
├── tsconfig.json
├── webpack.dev.config.js
└── webpack.prod.config.js
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | public/*
3 | dist/*
4 | webpack.dev.config.js
5 | webpack.prod.config.js
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true,
5 | },
6 | extends: [
7 | 'plugin:vue/vue3-essential',
8 | '@vue/standard',
9 | '@vue/typescript/recommended',
10 | ],
11 | parserOptions: {
12 | ecmaVersion: 2020
13 | },
14 | rules: {
15 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
16 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
17 | 'space-before-function-paren': ['error', 'never'], // 函数名后面是否需要个空格
18 | 'comma-dangle': ['off', 'always'], // 是否可以在最后一项添加逗号
19 | semi: ['warn', 'always'],
20 | quotes: ['warn', 'single']
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw?
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # webpack-vue-cli
2 | 基于webpack5+ webpack-chain + vue3.0+typescript+webpack-cli4从零搭建的vue3脚手架。
3 |
4 | # 适用性
5 | 可用于学习webpack5 + vue3 + ts的脚手架搭建方案,但也可以直接用于项目!。支持js,jsx,ts,tsx的vue3写法。
6 |
7 | # 不足性
8 | 由于市面上基本上还看不到一套webpack5+webpack-cli4+vue3+ts的从零实现的脚手架,所以我前期确实踩了很多很多的坑。
9 |
10 | # 推荐
11 | 推荐使用npm安装
12 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | // 在babel7.4.0之后所有的polyfill操作都依赖core-js
2 | /**
3 | * 用preset-env来转换语法和polyfill,
4 | * 然后再利用@babel/plugin-transform-runtime来引入helpers跟generator做到代码重复利用
5 | */
6 | module.exports = {
7 | presets: [ // 预设,可以理解为一系列封装好的插件的集合
8 | // '@vue/babel-preset-jsx', // 让vue支持jsx跟tsx,但是vue3不能使用这个预设,需使用@vue/babel-plugin-jsx这个插件
9 | [
10 | '@babel/preset-env', // 添加preset-env预设做语法转换,preset-env能将最新的语法转换为ecmascript5的写法
11 | {
12 | corejs: 3, // corejs版本,core-js@3废弃了babel-polyfill,实现了完全无污染的API转译
13 | useBuiltIns: 'usage', // 不需要手动在代码里写import‘@babel/polyfilll’,打包时会自动根据实际代码的使用情况,结合 .browserslistrc 引入代码里实际会用到 polyfilll部分模块
14 | }
15 | ]
16 | ],
17 | plugins: [ // 插件,使用插件去对js做对应的功能
18 | '@vue/babel-plugin-jsx', // Vue 3 Babel JSX 插件, 如果要在vue3中使用render函数,必须使用这个插件,而不是@vue/babel-preset-jsx,这个坑我踩了几天,不然会出现h is not defined的错误。https://github.com/vuejs/jsx-next/blob/dev/packages/babel-plugin-jsx/README-zh_CN.md
19 | // @babel/plugin-proposal-decorators 和 @babel/plugin-proposal-class-properties 让项目中可以使用装饰器写法,但是Vue3中一般也不使用了
20 | ['@babel/plugin-proposal-decorators', { // 装饰器插件
21 | legacy: true
22 | }],
23 | '@babel/plugin-proposal-class-properties', // 类属性插件
24 | [
25 | '@babel/plugin-transform-runtime', // 利用runtime做helpers跟regenerator设置
26 | {
27 | corejs: false,
28 | helpers: true,
29 | useESModules: false,
30 | regenerator: true,
31 | absoluteRuntime: './node_modules'
32 | }
33 | ]
34 | ]
35 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cli-d-component",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "scripts": {
6 | "dev": "webpack serve --config webpack.dev.config.js --progress",
7 | "build": "webpack --config webpack.prod.config.js",
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "author": "dudapeng",
11 | "license": "ISC",
12 | "description": "vue webpack components",
13 | "devDependencies": {
14 | "@babel/core": "^7.12.10",
15 | "@babel/plugin-proposal-decorators": "^7.12.1",
16 | "@babel/plugin-transform-runtime": "^7.12.10",
17 | "@babel/preset-env": "^7.12.10",
18 | "@typescript-eslint/eslint-plugin": "^4.10.0",
19 | "@typescript-eslint/parser": "^4.10.0",
20 | "@vue/babel-plugin-jsx": "^1.0.0-rc.5",
21 | "@vue/babel-preset-jsx": "^1.2.4",
22 | "@vue/compiler-sfc": "^3.0.0",
23 | "@vue/eslint-config-standard": "^6.0.0",
24 | "@vue/eslint-config-typescript": "^7.0.0",
25 | "@webpack-cli/serve": "^1.1.0",
26 | "autoprefixer": "^8.6.5",
27 | "babel-loader": "^8.2.2",
28 | "clean-webpack-plugin": "^3.0.0",
29 | "core-js": "^3.8.1",
30 | "css-loader": "^5.0.1",
31 | "cssnano": "^4.1.10",
32 | "eslint": "^7.15.0",
33 | "eslint-loader": "^4.0.2",
34 | "eslint-plugin-import": "^2.22.1",
35 | "eslint-plugin-node": "^11.1.0",
36 | "eslint-plugin-promise": "^4.2.1",
37 | "eslint-plugin-standard": "^5.0.0",
38 | "eslint-plugin-vue": "^7.2.0",
39 | "file-loader": "^6.2.0",
40 | "fork-ts-checker-webpack-plugin": "^6.0.4",
41 | "html-loader": "^1.3.2",
42 | "html-webpack-plugin": "^4.5.0",
43 | "mini-css-extract-plugin": "^1.3.3",
44 | "postcss-loader": "^4.1.0",
45 | "sass": "^1.30.0",
46 | "sass-loader": "^10.1.0",
47 | "style-loader": "^2.0.0",
48 | "svg-sprite-loader": "^5.2.1",
49 | "svgo": "^1.3.2",
50 | "svgo-loader": "^2.2.1",
51 | "ts-loader": "^8.0.11",
52 | "typescript": "^4.1.2",
53 | "url-loader": "^4.1.1",
54 | "vue-loader": "^16.0.0-beta.7",
55 | "webpack": "^5.10.3",
56 | "webpack-chain": "^6.5.1",
57 | "webpack-cli": "^4.2.0",
58 | "webpack-dev-server": "^3.11.0"
59 | },
60 | "dependencies": {
61 | "vue": "^3.0.0",
62 | "vue-class-component": "^7.2.6",
63 | "vue-property-decorator": "^9.1.2"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-var-requires
2 | const autoprefixer = require('autoprefixer');
3 | module.exports = {
4 | plugins: [
5 | autoprefixer(), // 添加浏览器前缀
6 | /* require("cssnano")({
7 | preset: ['default', {
8 | mergeLonghand: false,
9 | cssDeclarationSorter: false
10 | }]
11 | }) */
12 | ]
13 | };
14 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | cli-d-component1
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { defineComponent } from 'vue';
3 |
4 | export default defineComponent({
5 | render: function() {
6 | const a = 12345;
7 | const b: number = a;
8 | return {b + 1}
;
9 | }
10 | });
11 |
--------------------------------------------------------------------------------
/src/a.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bendajun/webpack-vue-cli/5b1fd3100e9038fdbaf8d0b467bdff451d2d4752/src/a.png
--------------------------------------------------------------------------------
/src/b.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bendajun/webpack-vue-cli/5b1fd3100e9038fdbaf8d0b467bdff451d2d4752/src/b.jpg
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue';
2 | import App from './App';
3 | import imgsrc from './b.jpg';
4 |
5 | const app = createApp(App);
6 | app.mount('#app');
7 |
8 | const img = document.createElement('img');
9 | img.setAttribute('src', imgsrc);
10 | document.querySelector('body')?.appendChild(img);
11 |
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-types */
2 | /* eslint-disable @typescript-eslint/no-explicit-any */
3 | declare module '*.vue' {
4 | import type { DefineComponent } from 'vue';
5 | const component: DefineComponent<{}, {}, any>;
6 | export default component;
7 | }
8 |
--------------------------------------------------------------------------------
/src/typings/images.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg'
2 | declare module '*.png'
3 | declare module '*.jpg'
4 | declare module '*.jpeg'
5 | declare module '*.gif'
6 | declare module '*.bmp'
7 | declare module '*.tiff'
8 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | // 编译选项
4 | "target": "esnext", // 编译输出目标ES版本
5 | "module": "esnext", // 采用的模块系统
6 | "strict": true, // 以严格模式解析
7 | "jsx": "preserve",
8 | "importHelpers": true,
9 | "moduleResolution": "node", // 如何处理模块
10 | "experimentalDecorators": true, // 启用装饰器
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入
13 | "strictPropertyInitialization": false, // 定义一个变量就必须给它一个初始值
14 | "allowJs": true, // 允许编辑javascript文件
15 | "sourceMap": true, // 是否包含可以用于 debug 的 sourceMap
16 | "noImplicitThis": false, // 忽略 this 的类型检查, Raise error on this expressions with an implied any type.
17 | "baseUrl": ".", // 解析非相对模块名的基准目录
18 | "pretty": true, // 给错误和消息设置样式,使用颜色和上下文
19 | "types": [ // 设置引入的定义文件
20 | "webpack-env",
21 | ],
22 | "paths": {
23 | "@/*": [
24 | "src/*"
25 | ]
26 | },
27 | "lib": [
28 | // 编译过程中需要引入的库文件的列表
29 | "esnext",
30 | "dom",
31 | "dom.iterable",
32 | "scripthost"
33 | ]
34 | },
35 | "include": [
36 | // ts 管理的文件
37 | "src/**/*.ts",
38 | "src/**/*.tsx",
39 | "src/**/*.vue",
40 | ".src//typings/images.d.ts",
41 | ],
42 | "exclude": [ // ts 排除的文件,不编译这些目录里的文件
43 | "node_modules"
44 | ]
45 | }
46 |
--------------------------------------------------------------------------------
/webpack.dev.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const Config = require('webpack-chain');
4 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
5 | const { CleanWebpackPlugin } = require('clean-webpack-plugin');
6 |
7 | const config = new Config();
8 | const publicPath = '/';
9 | // webpack-cli4 + webpack5 启动devServe是webpack serve了,而不是webpack-dev-server
10 | // webpack5 内部实现了cache-loader的效果
11 |
12 | config
13 | .target('web') // webpack5需要加这个,不然热更新不会手动刷新页面
14 | .mode('development')
15 | .context(path.resolve(__dirname, '.')) // 指定项目根目录
16 | .entry('app') // 指定入口文件名称为app
17 | .add('./src/main.ts') // 入口文件
18 | .end()
19 | .output
20 | .path(path.join(__dirname, './dist')) // webpack打包输出的文件夹
21 | .filename('js/[name].[contenthash:10].js') // 打包出来的bundle名称为[name].[contenthash:10].js
22 | .publicPath(publicPath) // 资源文件前缀
23 | .end()
24 | .resolve
25 | .extensions
26 | .add('.js').add('.jsx').add('.ts').add('.tsx').add('.vue') // 使用着希望文件时,导入时可以省略后缀
27 | .end()
28 | .alias // 设置别名
29 | .set('@', path.resolve(__dirname, './src'))
30 | .set('vue$', 'vue/dist/vue.runtime.esm-bundler.js') // 开发环境使用这个vue.js,参照vue-cli的vue3项目
31 | .end()
32 | .end()
33 | .module
34 | .rule('js')
35 | .test(/\.m?jsx?$/) // 对mjs、mjsx、js、jsx文件进行babel配置
36 | .exclude // 排除node_modules
37 | .add(filepath => /node_modules/.test(filepath))
38 | .end()
39 | .use('babel-loader')
40 | .loader('babel-loader')
41 | .end()
42 | .end()
43 | .rule('ts') // rule起的名字只是语义化,方便理解,和导出配置或者修改配置时对应
44 | .test(/\.tsx?$/) // 匹配处理后缀是ts或者tsx结尾的文件
45 | .use('babel-loader')
46 | .loader('babel-loader')
47 | .end()
48 | .use('ts-loader') // use也是语义化的名字,同rule
49 | .loader('ts-loader') // 这里才是使用ts-loader去处理匹配的文件
50 | .options({ // 这里对ts-loader进行相关的配置, 创建个tsconfig.json文件,默认会读取和使用这个文件
51 | transpileOnly: true, // 开启时: 编译器仅做语言转换不做类型检查,加快编译速度,但是静态类型检查中获得的许多好处将丢失。所以结合插件ForkTsCheckerWebpackPlugin用于新建进程执行类型检查,为此你需要关闭ts-loader自身的类型检查功能,即设置transpileOnly为true。
52 | appendTsSuffixTo: ['\\.vue$'], // 给vue文件添加个.ts或.tsx后缀,vue单文件组件中假如使用了 lang="ts", ts-loader需要配置 appendTsSuffixTo: [/\.vue$/],用来给 .vue文件添加个 .ts后缀用于编译,因为tsc不知道如何处理. vue文件结尾的文件
53 | happyPackMode: false,
54 | })
55 | .end()
56 | .end()
57 | .rule('vue')
58 | .test(/\.vue$/) // 匹配处理.vue后缀文件
59 | .use('vue-loader')
60 | .loader('vue-loader')
61 | .end()
62 | .end()
63 | .rule('sass')
64 | .test(/\.(sass|scss)$/) // 处理sass和scss文件
65 | .use('style-loader') // 开发环境用:style-loader 是将js中的css文件提取出来,放在style标签中,style-loader内部实现了热模块替换(HMR,一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块))
66 | .loader('style-loader')
67 | .end()
68 | .use("css-loader") // 处理css
69 | .loader("css-loader")
70 | .end()
71 | /**
72 | * autofixer是postcss的功能插件,主要是给css中的一些属性添加-webkit-这种前缀做兼容的,postcss-loader则是webpack的loader组件,主要作用是webpack在读取css模块的时候调用postcss和postcss的插件对css内容做兼容性处理的。
73 | * postcss-loader配置options的过程实际上是为postcss配置需要的插件
74 | */
75 | .use('postcss-loader') // 兼容性处理css
76 | .loader('postcss-loader')
77 | .options({
78 | postcssOptions: { // 指定配置文件,如果不指定的话其实是默认去根路径找postcss.config.js这个文件的,指定的好处就是可以指定特定的文件名
79 | config: path.resolve(__dirname, './postcss.config.js')
80 | }
81 | })
82 | .end()
83 | .use('sass-loader') // 先将sass语法转换为css语法
84 | .loader('sass-loader')
85 | .end()
86 | .end()
87 | .rule('eslint')
88 | .exclude
89 | .add(/node_modules/)
90 | .end()
91 | .test(/\.(vue|(j|t)sx?)$/)// 处理.vue、.js、.jsx、.ts、.tsx文件
92 | .use('eslint-loader')
93 | .loader(require.resolve('eslint-loader'))
94 | .options({
95 | emitWarning: false, // 出现警告是否终止webpack编译
96 | emitError: true,
97 | })
98 | .end()
99 | .end()
100 | .rule('images') // 处理png|jpe?g|gif|webp图片 处理图片需要安装 url-loader file-loader,安装file-loader的原因是url-loader是依赖它
101 | .test(/\.(png|jpe?g|gif|webp)$/)
102 | .use('url-loader')
103 | .loader('url-loader')
104 | .options({
105 | limit: 8 * 1024, // 当图片大小小于8kb,就会被处理为base64位字符串,优点:减少请求数量(减轻服务器压力),缺点:图片体积会更大(文件加载速度更慢)
106 | name: '[hash:10].[ext]', // [ext]取文件原来扩展名
107 | outputPath: 'imgs', // 放在img文件夹下
108 | })
109 | .end()
110 | .end()
111 | .rule('icons')
112 | .test(/\.svg$/) // 处理svg图片
113 | .include
114 | .add(path.resolve(__dirname, './src/icons')) // 处理的svg放在此路径下
115 | .end()
116 | .use('svg-sprite-loader')
117 | .loader('svg-sprite-loader')
118 | .options({
119 | symbolId: 'icon-[name]'
120 | })
121 | .end()
122 | .use('svgo-loader')
123 | .loader('svgo-loader')
124 | .end()
125 | .end()
126 | .rule('html') // html-loader是专门处理html文件中的img图片(负责引入img,从而能被url-loader进行处理)
127 | .test(/\.html$/)
128 | .use('html-loader')
129 | .loader('html-loader')
130 | .end()
131 | .end()
132 | .rule('other') // 打包其他资源(除了html/js/css等资源以外的资源),将这些资源不压缩,不转换统一打包过去,相当于复制过去
133 | .test(/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/)
134 | .test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/i)
135 | .use('file-loader')
136 | .loader('file-loader') // 用file-loader来处理这些其他资源,可以原封不动的打包过去
137 | .options({
138 | name: '[hash:10].[ext]',
139 | })
140 | .end()
141 | .end()
142 | config
143 | .plugin('fork-ts-checker')
144 | .use(ForkTsCheckerWebpackPlugin, [{ // 插件ForkTsCheckerWebpackPlugin用于新建进程执行类型检查,为此你需要关闭ts-loader自身的类型检查功能,即设置transpileOnly为true
145 | eslint: { // 这个是结合eslint使用时的
146 | files: './src/**/*.{ts,tsx,js,jsx}' // required - same as command `eslint ./src/**/*.{ts,tsx,js,jsx} --ext .ts,.tsx,.js,.jsx`
147 | },
148 | typescript: {
149 | extensions: {
150 | vue: {
151 | enabled: true, // 如果为true,则启用Vue单个文件组件支持。
152 | compiler: '@vue/compiler-sfc' // 默认值是vue-template-compiler,不适用于vue3,用于解析.vue文件的编译器的程序包名称
153 | }
154 | },
155 | diagnosticOptions: {
156 | semantic: true,
157 | syntactic: false
158 | }
159 | }
160 | }])
161 | .end()
162 | .plugin('vue-loader-plugin') // vue-loader必须要添加vue-loader-plugin
163 | .use(require('vue-loader').VueLoaderPlugin, [])// 是将你定义过的其它规则复制并应用到 .vue 文件里相应语言的块。例如,如果你有一条匹配 /\.js$/ 的规则,那么它会应用到 .vue 文件里的