├── .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 文件里的