手把手教你从0开始搭建一个vue项目(完结)
前言
上一節(jié)webpack實戰(zhàn)之(手把手教你從0開始搭建一個vue項目)最后我們完成了css樣式的配置:
webpack.config.js:
const path = require("path"); const config = new (require("webpack-chain"))(); const isDev = process.env.WEBPACK_DEV_SERVER; config.context(path.resolve(__dirname, ".")) //webpack上下文目錄為項目根目錄.entry("app") //入口文件名稱為app.add("./src/main.ts") //入口文件為./src/main.ts.end().output.path(path.join(__dirname,"./dist")) //webpack輸出的目錄為根目錄的dist目錄.filename("[name].[hash:8].js").end().resolve.extensions.add(".js").add(".jsx").add(".ts").add(".tsx").add(".vue") //配置以.js等結尾的文件當模塊使用的時候都可以省略后綴.end().end().module.rule("type-script").test(/\.tsx?$/) //loader加載的條件是ts或tsx后綴的文件.use("ts-loader").loader("ts-loader").options({ //ts-loader相關配置transpileOnly: true,appendTsSuffixTo: ['\\.vue$']}).end().end().rule("vue").test(/\.vue$/)// 匹配.vue文件.use("vue-loader").loader("vue-loader").end().end().rule("sass").test( /\.(sass|scss)$/)//sass和scss文件.use("extract-loader")//提取css樣式到單獨css文件.loader(require('mini-css-extract-plugin').loader).options({hmr: isDev //開發(fā)環(huán)境開啟熱載}).end().use("css-loader")//加載css模塊.loader("css-loader").end().use("postcss-loader")//處理css樣式.loader("postcss-loader").options( {config: {path: path.resolve(__dirname, "./postcss.config.js")}}).end().use("sass-loader")//sass語法轉css語法.loader("sass-loader").end().end().end().plugin("vue-loader-plugin")//vue-loader必須要添加vue-loader-plugin.use(require("vue-loader").VueLoaderPlugin,[]).end().plugin("html")// 添加html-webpack-plugin插件.use(require("html-webpack-plugin"),[{template: path.resolve(__dirname,"./public/index.html"), //指定模版文件chunks:["app"], //指定需要加載的chunkinject: "body" //指定script腳本注入的位置為body}]).end().plugin("extract-css")//提取css樣式到單獨css文件.use(require('mini-css-extract-plugin'), [{filename: "css/[name].css",chunkFilename: "css/[name].css"}]).end().devServer.host("0.0.0.0") //為了讓外部服務訪問.port(8090) //當前端口號.hot(true) //熱載.open(true) //開啟頁面 module.exports = config.toConfig();配置
babel
看一下我們當前項目中的測試代碼,src/app.vue:
<template><div class="app-container">{{this.msg}}</div> </template><script lang="ts">import {Vue, Component} from "vue-property-decorator";@Componentexport default class App extends Vue {msg="hello world";user={name: "yasin"};created(){const name=this.user?.name;console.log("name");}} </script><style scoped lang="scss"> .app-container{color: red; } </style>比如我們需要用到最新的optional-chaining語法:
const name=this.user?.name;我們試著運行一下我們的demo:
... ERROR in ./src/app.vue?vue&type=script&lang=ts& (./node_modules/ts-loader??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/app.vue?vue&type=script&lang=ts&) 12:31 Module parse failed: Unexpected token (12:31) File was processed with these loaders:* ./node_modules/ts-loader/index.js* ./node_modules/vue-loader/lib/index.js You may need an additional loader to handle the result of these loaders. | } | created() { > const name = this.user?.name; | console.log("name"); | }@ ./src/app.vue?vue&type=script&lang=ts& 1:0-160 1:176-179 1:181-338 1:181-338@ ./src/app.vue@ ./src/main.ts@ multi ./src/main.ts可以發(fā)現,報錯了! 提示我們“該語法解析器不能解析”,有小伙伴可能發(fā)現了,在我們demo中我們還用到了:
import {Vue, Component} from "vue-property-decorator";@Componentexport default class App extends Vue {裝飾器語法,那demo之前也沒報錯啊,為什么呢? 因為我們用的是ts的解析器,ts解析器是可以解析裝飾器這種語法的:
<script lang="ts">我們把lang="ts"去掉試試:
<template><div class="app-container">{{this.msg}}</div> </template><script>import {Vue, Component} from "vue-property-decorator";@Componentexport default class App extends Vue {msg="hello world";user={name: "yasin"};created(){const name=this.user?.name;console.log("name");}} </script><style scoped lang="scss"> .app-container{color: red; } </style> ERROR in ./src/app.vue?vue&type=script&lang=js& (./node_modules/vue-loader/lib??vue-loader-options!./src/app.vue?vue&type=script&lang=js&) 8:0 Module parse failed: Unexpected character '@' (8:0) File was processed with these loaders:* ./node_modules/vue-loader/lib/index.js You may need an additional loader to handle the result of these loaders. | import {Vue, Component} from "vue-property-decorator"; | > @Component | export default class App extends Vue { | msg="hello world";@ ./src/app.vue?vue&type=script&lang=js& 1:0-115 1:131-134 1:136-248 1:136-248@ ./src/app.vue@ ./src/main.ts@ multi ./src/main.tsok! 可以看到,這次報的是裝飾器語法解析失敗了,知道原因后,我們該怎么解決呢? 看過前面babel系列文章的童鞋應該是立馬就想到了babel,那么接下來我們就配置一下babel。
@babel/core(babel核心)
yarn add -D @babel/core || npm install -D @babel/core@babel/preset-env (env預設)
做es6語法轉換、添加polyfill。
yarn add -D @babel/preset-env || npm install -D @babel/preset-env@babel/plugin-transform-runtime (運行時插件)
添加一些babel轉換時候的幫助函數。
yarn add -D @babel/plugin-transform-runtime || npm install -D @babel/plugin-transform-runtimebabel-loader (es加載器)
yarn add -D babel-loader || npm install -D babel-loadercore-js (preset-env設置polyfill的依賴)
yarn add -D core-js || npm install -D core-jsok, 安裝完babel的一些依賴后,我們開始配置webpack,首先是對js、jsx的babel配置:
.module.rule('js').test(/\.m?jsx?$/) //對mjs、mjsx、js、jsx文件進行babel配置.exclude.add(filepath => {// Don't transpile node_modulesreturn /node_modules/.test(filepath)}).end().use("babel-loader").loader("babel-loader").end().end()然后是ts、tsx的配置:
.rule("type-script").test(/\.tsx?$/) //loader加載的條件是ts或tsx后綴的文件.use("babel-loader").loader("babel-loader").end()整個配置文件,webpack.config.js:
const path = require("path"); const config = new (require("webpack-chain"))(); const isDev = process.env.WEBPACK_DEV_SERVER; config.context(path.resolve(__dirname, ".")) //webpack上下文目錄為項目根目錄.entry("app") //入口文件名稱為app.add("./src/main.ts") //入口文件為./src/main.ts.end().output.path(path.join(__dirname,"./dist")) //webpack輸出的目錄為根目錄的dist目錄.filename("[name].[hash:8].js").end().resolve.extensions.add(".js").add(".jsx").add(".ts").add(".tsx").add(".vue") //配置以.js等結尾的文件當模塊使用的時候都可以省略后綴.end().end().module.rule('js').test(/\.m?jsx?$/) //對mjs、mjsx、js、jsx文件進行babel配置.exclude.add(filepath => {// Don't transpile node_modulesreturn /node_modules/.test(filepath)}).end().use("babel-loader").loader("babel-loader").end().end().rule("type-script").test(/\.tsx?$/) //loader加載的條件是ts或tsx后綴的文件.use("babel-loader").loader("babel-loader").end().use("ts-loader").loader("ts-loader").options({ //ts-loader相關配置transpileOnly: true,appendTsSuffixTo: ['\\.vue$']}).end().end().rule("vue").test(/\.vue$/)// 匹配.vue文件.use("vue-loader").loader("vue-loader").end().end().rule("sass").test( /\.(sass|scss)$/)//sass和scss文件.use("extract-loader")//提取css樣式到單獨css文件.loader(require('mini-css-extract-plugin').loader).options({hmr: isDev //開發(fā)環(huán)境開啟熱載}).end().use("css-loader")//加載css模塊.loader("css-loader").end().use("postcss-loader")//處理css樣式.loader("postcss-loader").options( {config: {path: path.resolve(__dirname, "./postcss.config.js")}}).end().use("sass-loader")//sass語法轉css語法.loader("sass-loader").end().end().end().plugin("vue-loader-plugin")//vue-loader必須要添加vue-loader-plugin.use(require("vue-loader").VueLoaderPlugin,[]).end().plugin("html")// 添加html-webpack-plugin插件.use(require("html-webpack-plugin"),[{template: path.resolve(__dirname,"./public/index.html"), //指定模版文件chunks:["app"], //指定需要加載的chunkinject: "body" //指定script腳本注入的位置為body}]).end().plugin("extract-css")//提取css樣式到單獨css文件.use(require('mini-css-extract-plugin'), [{filename: "css/[name].css",chunkFilename: "css/[name].css"}]).end().devServer.host("0.0.0.0") //為了讓外部服務訪問.port(8090) //當前端口號.hot(true) //熱載.open(true) //開啟頁面 module.exports = config.toConfig();然后我們再次運行npm run dev指令:
ERROR in ./src/app.vue?vue&type=script&lang=js& (./node_modules/babel-loader/lib!./node_modules/vue-loader/lib??vue-loader-options!./src/app.vue?vue&type=script&lang=js&) Module build failed (from ./node_modules/babel-loader/lib/index.js): SyntaxError: xxx/webpack-vue-demo/src/app.vue: Support for the experimental syntax 'decorators-legacy' isn't currently enabled (8:1):6 | import {Vue, Component} from "vue-property-decorator";7 | > 8 | @Component| ^9 | export default class App extends Vue {10 | msg="hello world";11 | user={at Parser._raise (xxx/webpack-vue-demo/node_modules/@babel/parser/lib/index.js:757:17)at Parser.raiseWithData (xxx/webpack-vue-demo/node_modules/@babel/parser/lib/index.js:750:17)可以看到,還是報錯,這是為什么呢? 因為我們沒有對babel進行配置,我們在項目根目錄創(chuàng)建一個babel.config.js文件,然后進行babel配置:
babel.config.js
module.exports = {presets: [["@babel/preset-env", //添加preset-env預設做語法轉換跟polyfill添加{corejs: 3,useBuiltIns: "usage",modules: false}]],plugins: [["@babel/plugin-transform-runtime", //利用runtime做helpers跟regenerator設置{corejs: false,helpers: true,useESModules: false,regenerator: true,absoluteRuntime: "./node_modules"}]] };配置我就不詳細解說了,前面babel的文章中都有解析。
ok,我們再次運行npm run dev:
ERROR in ./src/app.vue?vue&type=script&lang=js& (./node_modules/babel-loader/lib!./node_modules/vue-loader/lib??vue-loader-options!./src/app.vue?vue&type=script&lang=js&) Module build failed (from ./node_modules/babel-loader/lib/index.js): SyntaxError: xxx/webpack-vue-demo/src/app.vue: Support for the experimental syntax 'decorators-legacy' isn't currently enabled (8:1):6 | import {Vue, Component} from "vue-property-decorator";7 | > 8 | @Component| ^9 | export default class App extends Vue {10 | msg="hello world";11 | user={ok, 還是報裝飾器語法無法解析,看過前面babel的同學應該是知道的,babel-preset-env默認是不添加“decorators”插件的,因為“decorators”還在提案2階段不穩(wěn)定, 那我們需要在js中使用“decorators”怎么辦呢? 我們前面也有文章單獨介紹過“裝飾器”,看過的童鞋應該是知道的,
安裝“decorators”插件:
yarn add -D @babel/plugin-proposal-decorators || npm install -D @babel/plugin-proposal-decorators安裝“類屬性”插件:
yarn add -D @babel/plugin-proposal-class-properties || npm install -D @babel/plugin-proposal-class-properties配置babel.config.js:
module.exports = {presets: [["@babel/preset-env", //添加preset-env預設做語法轉換跟polyfill添加{corejs: 3,useBuiltIns: "usage",modules: false}]],plugins: [["@babel/plugin-proposal-decorators",{ //裝飾器插件legacy: true}],"@babel/plugin-proposal-class-properties", //類屬性插件["@babel/plugin-transform-runtime", //利用runtime做helpers跟regenerator設置{corejs: false,helpers: true,useESModules: false,regenerator: true,absoluteRuntime: "./node_modules"}]] };注意??: 兩個插件的順序一定不要弄反哦!
然后我們再次運行npm run dev:
npm run dev ... ? 「wdm」: Compiled successfully.可以看到,最后顯示編譯成功了,我就不截圖了,瀏覽器中應該會顯示“hello world”文字的。
同樣,我們把lang改成ts也是可以正常運行的:
src/app.vue
<template><div class="app-container">{{this.msg}}</div> </template><script lang="ts">import {Vue, Component} from "vue-property-decorator";@Componentexport default class App extends Vue {msg="hello world";user={name: "yasin"};created(){const name=this.user?.name;console.log("name");}} </script><style scoped lang="scss"> .app-container{color: red; } </style>因為我們在webpack的loader中給ts也添加了babel配置:
.rule("type-script").test(/\.tsx?$/) //loader加載的條件是ts或tsx后綴的文件.use("babel-loader").loader("babel-loader").end().use("ts-loader").loader("ts-loader").options({ //ts-loader相關配置transpileOnly: true,appendTsSuffixTo: ['\\.vue$']}).end().end()jsx
上一節(jié)我們還說了,我們的工程是需要支持js、jsx、ts、tsx的,現在js跟ts都是支持的,那么我們需要怎么支持jsx跟tsx呢?
ok! 在vue中vue官方已經給我們提供了一個第三方庫供我們使用jsx,下面我們安裝一下:
@vue/babel-preset-jsx
yarn add -D @vue/babel-preset-jsx || npm install -D @vue/babel-preset-jsx然后直接配置到我們的babel配置文件即可,
babel.config.js:
module.exports = {presets: ["@vue/babel-preset-jsx", //vue支持jsx跟tsx["@babel/preset-env", //添加preset-env預設做語法轉換跟polyfill添加{corejs: 3,useBuiltIns: "usage",modules: false}]],plugins: [["@babel/plugin-proposal-decorators",{ //裝飾器插件legacy: true}],"@babel/plugin-proposal-class-properties", //類屬性插件["@babel/plugin-transform-runtime", //利用runtime做helpers跟regenerator設置{corejs: false,helpers: true,useESModules: false,regenerator: true,absoluteRuntime: "./node_modules"}]] };沒錯! 就是這么簡單~ 為了驗證一下我們在src目錄下創(chuàng)建一個app.tsx文件,
src/app.tsx:
import {Vue, Component} from "vue-property-decorator";@Component export default class AppTsx extends Vue {msg = "hello tsx";render(h) {return (<div>{this.msg}</div>);} }代碼很簡單,我們直接用tsx輸出一個msg為“hello tsx”,然后我們修改一下main.ts入口文件,
main.ts:
import Vue from "vue"; // import App from "./app.vue"; import App from "./app";new Vue({el: "#app",render: (h) => h(App) });然后我們運行npm run dev:
可以看到,頁面中正常顯示了我們的內容。
eslint
eslint(eslint核心)
yarn add -D eslint || npm install -D eslinteslint-loader (eslint加載器)
yarn add -D eslint-loader || npm install -D eslint-loadereslint-plugin-vue (解析vue模版)
yarn add -D eslint-plugin-vue || npm install -D eslint-plugin-vue@typescript-eslint/eslint-plugin(ts eslint插件)
yarn add -D @typescript-eslint/eslint-plugin || npm install -D @typescript-eslint/eslint-plugin@typescript-eslint/parser (ts解析器,供eslint-plugin-vue解析vue模版以外語法使用)
yarn add -D @typescript-eslint/parser || npm install -D @typescript-eslint/parser@vue/eslint-config-typescript (vue中對ts寫法的一些好的建議)
yarn add -D @vue/eslint-config-typescript || npm install -D @vue/eslint-config-typescripteslint-plugin-prettier (比較好的一些js語法建議)
yarn add -D eslint-plugin-prettier || npm install -D eslint-plugin-prettier@vue/eslint-config-prettier (vue推薦的比較好的寫法)
yarn add -D @vue/eslint-config-prettier || npm install -D @vue/eslint-config-prettierok, 我們已經安裝了我們需要的一些eslint依賴,然后我們去webpack配置中添加eslint-loader,
webpack.config.js:
const path = require("path"); const config = new (require("webpack-chain"))(); const isDev = !!process.env.WEBPACK_DEV_SERVER; config.context(path.resolve(__dirname, ".")) //webpack上下文目錄為項目根目錄.entry("app") //入口文件名稱為app.add("./src/main.ts") //入口文件為./src/main.ts.end().output.path(path.join(__dirname,"./dist")) //webpack輸出的目錄為根目錄的dist目錄.filename("[name].[hash:8].js").end().resolve.extensions.add(".js").add(".jsx").add(".ts").add(".tsx").add(".vue") //配置以.js等結尾的文件當模塊使用的時候都可以省略后綴.end().end().module.rule('js').test(/\.m?jsx?$/) //對mjs、mjsx、js、jsx文件進行babel配置.exclude.add(filepath => {// Don't transpile node_modulesreturn /node_modules/.test(filepath)}).end().use("babel-loader").loader("babel-loader").end().end().rule("type-script").test(/\.tsx?$/) //loader加載的條件是ts或tsx后綴的文件.use("babel-loader").loader("babel-loader").end().use("ts-loader").loader("ts-loader").options({ //ts-loader相關配置transpileOnly: true,appendTsSuffixTo: ['\\.vue$']}).end().end().rule("vue").test(/\.vue$/)// 匹配.vue文件.use("vue-loader").loader("vue-loader").end().end().rule("sass").test( /\.(sass|scss)$/)//sass和scss文件.use("extract-loader")//提取css樣式到單獨css文件.loader(require('mini-css-extract-plugin').loader).options({hmr: isDev //開發(fā)環(huán)境開啟熱載}).end().use("css-loader")//加載css模塊.loader("css-loader").end().use("postcss-loader")//處理css樣式.loader("postcss-loader").options( {config: {path: path.resolve(__dirname, "./postcss.config.js")}}).end().use("sass-loader")//sass語法轉css語法.loader("sass-loader").end().end().rule('eslint')//添加eslint-loader.exclude.add(/node_modules/)//校驗的文件除node_modules以外.end().test(/\.(vue|(j|t)sx?)$/)//加載.vue、.js、.jsx、.ts、.tsx文件.use('eslint-loader').loader(require.resolve('eslint-loader')).options({emitWarning: true, //出現警告是否終止webpack編譯emitError: !isDev, //生成環(huán)境編譯報錯}).end().end().end().plugin("vue-loader-plugin")//vue-loader必須要添加vue-loader-plugin.use(require("vue-loader").VueLoaderPlugin,[]).end().plugin("html")// 添加html-webpack-plugin插件.use(require("html-webpack-plugin"),[{template: path.resolve(__dirname,"./public/index.html"), //指定模版文件chunks:["app"], //指定需要加載的chunkinject: "body" //指定script腳本注入的位置為body}]).end().plugin("extract-css")//提取css樣式到單獨css文件.use(require('mini-css-extract-plugin'), [{filename: "css/[name].css",chunkFilename: "css/[name].css"}]).end().devServer.host("0.0.0.0") //為了讓外部服務訪問.port(8090) //當前端口號.hot(true) //熱載.open(true) //開啟頁面 module.exports = config.toConfig();然后我們在根目錄創(chuàng)建一個eslint配置文件,.eslintrc.json:
{"env": {"node": true //主要爭對webpack配置文件等等node環(huán)境},"plugins": ["vue" //添加eslint-plugin-vue插件],"extends": ["eslint:recommended", //eslint推薦語法"plugin:vue/recommended", //使用vue推薦語法"@vue/typescript/recommended",//繼承typescript插件的recommended配置"@vue/prettier","@vue/prettier/@typescript-eslint"],"rules": {"semi": ["error","always"],"quotes": ["error","double"],"no-console": "error"} }eslint不熟的童鞋可以看前面關于eslint的文章。
有些文件我們不需要進行eslint校驗,所以我們直接在根目錄創(chuàng)建一個.eslintignore文件聲明,
.eslintignore:
node_modules/* public/* dist/* webpack.config.js然后我們運行npm run dev:
ERROR in ./src/app.tsx Module Error (from ./node_modules/eslint-loader/dist/cjs.js):/Users/ocj1/doc/h5/study/webpack/webpack-vue-demo/src/app.tsx1:9 warning Replace `Vue,·Component` with `·Vue,·Component·` prettier/prettier5:3 warning Delete `··` prettier/prettier7:3 warning Delete `··` prettier/prettier7:5 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types7:12 warning Argument 'h' should be typed @typescript-eslint/explicit-module-boundary-types7:12 warning 'h' is defined but never used @typescript-eslint/no-unused-vars8:1 warning Replace `········return·(?············<div>{this.msg}</div>?········)` with `····return·<div>{this.msg}</div>` prettier/prettier11:1 warning Delete `··` prettier/prettier12:2 warning Insert `?` prettier/prettier? 9 problems (0 errors, 9 warnings)0 errors and 6 warnings potentially fixable with the `--fix` option.@ ./src/main.ts 3:0-24 7:13-16@ multi ./src/main.tsERROR in ./src/main.ts Module Error (from ./node_modules/eslint-loader/dist/cjs.js):/Users/ocj1/doc/h5/study/webpack/webpack-vue-demo/src/main.ts3:24 warning Insert `;` prettier/prettier3:24 error Missing semicolon semi7:11 warning Replace `(h)` with `h` prettier/prettier8:4 warning Insert `?` prettier/prettier? 4 problems (1 error, 3 warnings)1 error and 3 warnings potentially fixable with the `--fix` option.@ multi ./src/main.ts app[0] Child HtmlWebpackCompiler:1 assetEntrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0[./node_modules/html-webpack-plugin/lib/loader.js!./public/index.html] 469 bytes {HtmlWebpackPlugin_0} [built] ? 「wdm」: Failed to compile.可以看到,報了很多警告跟錯誤,我們嘗試修復一下,為了方便,我們在package.json中聲明一個lint腳本,
package.json:
{"name": "webpack-vue-demo","version": "1.0.0","description": "","main": "index.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1","build": "rimraf dist && webpack --mode=production","dev": "webpack-dev-server --mode=development --progress","lint": "eslint ./src/* --fix"},...然后我們執(zhí)行npm run lint把一些能修復的修復掉:
... Module Error (from ./node_modules/eslint-loader/dist/cjs.js):xx/webpack/webpack-vue-demo/src/app.tsx7:3 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types7:10 warning Argument 'h' should be typed @typescript-eslint/explicit-module-boundary-types7:10 warning 'h' is defined but never used @typescript-eslint/no-unused-vars? 3 problems (0 errors, 3 warnings)可以看到有一些警告都是關于src/app.tsx文件的,
src/app.tsx:
import { Vue, Component } from "vue-property-decorator";@Component export default class AppTsx extends Vue {msg = "hello tsx";render(h) {return <div>{this.msg}</div>;} }說我們的render函數沒有定義返回值,ok! 我們直接加上返回值:
import { VNode } from "vue"; import { Vue, Component } from "vue-property-decorator";@Component export default class AppTsx extends Vue {msg = "hello tsx";render(h):VNode {return <div>{this.msg}</div>;} }然后說h變量沒用,ok,我們直接去掉:
import { VNode } from "vue"; import { Vue, Component } from "vue-property-decorator";@Component export default class AppTsx extends Vue {msg = "hello tsx";render():VNode {return <div>{this.msg}</div>;} }ok,解決完畢后,我們再次執(zhí)行npm run dev的時候就沒有錯誤跟警告信息了。
一般在開發(fā)環(huán)境為了更好的顯示錯誤信息,我們直接利用webpack.devServer的overlay顯示到頁面中去,
webpack.config.js:
const path = require("path"); const config = new (require("webpack-chain"))(); const isDev = !!process.env.WEBPACK_DEV_SERVER; config.context(path.resolve(__dirname, ".")) //webpack上下文目錄為項目根目錄.entry("app") //入口文件名稱為app.add("./src/main.ts") //入口文件為./src/main.ts.end().output.path(path.join(__dirname,"./dist")) //webpack輸出的目錄為根目錄的dist目錄.filename("[name].[hash:8].js").end().resolve.extensions.add(".js").add(".jsx").add(".ts").add(".tsx").add(".vue") //配置以.js等結尾的文件當模塊使用的時候都可以省略后綴.end().end().module.rule('js').test(/\.m?jsx?$/) //對mjs、mjsx、js、jsx文件進行babel配置.exclude.add(filepath => {// Don't transpile node_modulesreturn /node_modules/.test(filepath)}).end().use("babel-loader").loader("babel-loader").end().end().rule("type-script").test(/\.tsx?$/) //loader加載的條件是ts或tsx后綴的文件.use("babel-loader").loader("babel-loader").end().use("ts-loader").loader("ts-loader").options({ //ts-loader相關配置transpileOnly: true,appendTsSuffixTo: ['\\.vue$']}).end().end().rule("vue").test(/\.vue$/)// 匹配.vue文件.use("vue-loader").loader("vue-loader").end().end().rule("sass").test( /\.(sass|scss)$/)//sass和scss文件.use("extract-loader")//提取css樣式到單獨css文件.loader(require('mini-css-extract-plugin').loader).options({hmr: isDev //開發(fā)環(huán)境開啟熱載}).end().use("css-loader")//加載css模塊.loader("css-loader").end().use("postcss-loader")//處理css樣式.loader("postcss-loader").options( {config: {path: path.resolve(__dirname, "./postcss.config.js")}}).end().use("sass-loader")//sass語法轉css語法.loader("sass-loader").end().end().rule('eslint')//添加eslint-loader.exclude.add(/node_modules/)//校驗的文件除node_modules以外.end().test(/\.(vue|(j|t)sx?)$/)//加載.vue、.js、.jsx、.ts、.tsx文件.use('eslint-loader').loader(require.resolve('eslint-loader')).options({emitWarning: true, //把eslint報錯當成webpack警告emitError: !isDev, //把eslint報錯當成webapck的錯誤}).end().end().end().plugin("vue-loader-plugin")//vue-loader必須要添加vue-loader-plugin.use(require("vue-loader").VueLoaderPlugin,[]).end().plugin("html")// 添加html-webpack-plugin插件.use(require("html-webpack-plugin"),[{template: path.resolve(__dirname,"./public/index.html"), //指定模版文件chunks:["app"], //指定需要加載的chunkinject: "body" //指定script腳本注入的位置為body}]).end().plugin("extract-css")//提取css樣式到單獨css文件.use(require('mini-css-extract-plugin'), [{filename: "css/[name].css",chunkFilename: "css/[name].css"}]).end().devServer.host("0.0.0.0") //為了讓外部服務訪問.port(8090) //當前端口號.hot(true) //熱載.open(true) //開啟頁面.overlay({warnings: true,errors: true}) //webpack錯誤和警告信息顯示到頁面 module.exports = config.toConfig();fork-ts-checker-webpack-plugin
校驗ts語法,會把ts的一些報錯通過eslint展現出來。
yarn add -D fork-ts-checker-webpack-plugin || npm install -D fork-ts-checker-webpack-pluginok,然后我們把該插件配置到webpack:
babel.config.js
const path = require("path"); const config = new (require("webpack-chain"))(); const isDev = !!process.env.WEBPACK_DEV_SERVER; config.context(path.resolve(__dirname, ".")) //webpack上下文目錄為項目根目錄.entry("app") //入口文件名稱為app.add("./src/main.ts") //入口文件為./src/main.ts.end().output.path(path.join(__dirname,"./dist")) //webpack輸出的目錄為根目錄的dist目錄.filename("[name].[hash:8].js").end().resolve.extensions.add(".js").add(".jsx").add(".ts").add(".tsx").add(".vue") //配置以.js等結尾的文件當模塊使用的時候都可以省略后綴.end().end().module.rule('js').test(/\.m?jsx?$/) //對mjs、mjsx、js、jsx文件進行babel配置.exclude.add(filepath => {// Don't transpile node_modulesreturn /node_modules/.test(filepath)}).end().use("babel-loader").loader("babel-loader").end().end().rule("type-script").test(/\.tsx?$/) //loader加載的條件是ts或tsx后綴的文件.use("babel-loader").loader("babel-loader").end().use("ts-loader").loader("ts-loader").options({ //ts-loader相關配置transpileOnly: true, // disable type checker - we will use it in fork pluginappendTsSuffixTo: ['\\.vue$']}).end().end().rule("vue").test(/\.vue$/)// 匹配.vue文件.use("vue-loader").loader("vue-loader").end().end().rule("sass").test( /\.(sass|scss)$/)//sass和scss文件.use("extract-loader")//提取css樣式到單獨css文件.loader(require('mini-css-extract-plugin').loader).options({hmr: isDev //開發(fā)環(huán)境開啟熱載}).end().use("css-loader")//加載css模塊.loader("css-loader").end().use("postcss-loader")//處理css樣式.loader("postcss-loader").options( {config: {path: path.resolve(__dirname, "./postcss.config.js")}}).end().use("sass-loader")//sass語法轉css語法.loader("sass-loader").end().end().rule('eslint')//添加eslint-loader.exclude.add(/node_modules/)//校驗的文件除node_modules以外.end().test(/\.(vue|(j|t)sx?)$/)//加載.vue、.js、.jsx、.ts、.tsx文件.use('eslint-loader').loader(require.resolve('eslint-loader')).options({emitWarning: true, //把eslint報錯當成webpack警告emitError: !isDev, //把eslint報錯當成webapck的錯誤}).end().end().end().plugin("vue-loader-plugin")//vue-loader必須要添加vue-loader-plugin.use(require("vue-loader").VueLoaderPlugin,[]).end().plugin("html")// 添加html-webpack-plugin插件.use(require("html-webpack-plugin"),[{template: path.resolve(__dirname,"./public/index.html"), //指定模版文件chunks:["app"], //指定需要加載的chunkinject: "body" //指定script腳本注入的位置為body}]).end().plugin("extract-css")//提取css樣式到單獨css文件.use(require('mini-css-extract-plugin'), [{filename: "css/[name].css",chunkFilename: "css/[name].css"}]).end().plugin('fork-ts-checker') //配置fork-ts-checker.use(require('fork-ts-checker-webpack-plugin'), [{eslint: {files: './src/**/*.{ts,tsx,js,jsx,vue}' // required - same as command `eslint ./src/**/*.{ts,tsx,js,jsx} --ext .ts,.tsx,.js,.jsx`},typescript: {extensions: {vue: {enabled: true,compiler: "vue-template-compiler"},}}}]).end().devServer.host("0.0.0.0") //為了讓外部服務訪問.port(8090) //當前端口號.hot(true) //熱載.open(true) //開啟頁面.overlay({warnings: true,errors: true}) //webpack錯誤和警告信息顯示到頁面 module.exports = config.toConfig();ok, 配置完畢后我們簡單測試一下,比如我們的src/app.tsx:
import { VNode } from "vue"; import { Vue, Component } from "vue-property-decorator";@Component export default class AppTsx extends Vue {msg = "hello tsx";render(): VNode {return <div>{this.msg}</div>;} }然后我們在render方法中把msg的值改改:
import { VNode } from "vue"; import { Vue, Component } from "vue-property-decorator";@Component export default class AppTsx extends Vue {msg = "hello tsx";render(): VNode {this.msg = 1;return <div>{this.msg}</div>;} }我們直接把一個string類型的變量改成了number,然后我們運行npm run dev:
... ? 「wdm」: Compiled successfully. Issues checking in progress... ERROR in src/app.tsx 9:5-13 TS2322: Type '1' is not assignable to type 'string'.7 | 8 | render(): VNode {> 9 | this.msg = 1;| ^^^^^^^^10 | return <div>{this.msg}</div>;11 | }12 | } ERROR in src/app.tsx 9:5-13 TS2322: Type '1' is not assignable to type 'string'.7 | 8 | render(): VNode {> 9 | this.msg = 1;| ^^^^^^^^10 | return <div>{this.msg}</div>;11 | }12 | }ERROR in src/app.tsx 10:12-17 TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.8 | render(): VNode {9 | this.msg = 1;> 10 | return <div>{this.msg}</div>;| ^^^^^11 | }12 | }13 | ERROR in src/app.tsx 10:27-33 TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.8 | render(): VNode {9 | this.msg = 1;> 10 | return <div>{this.msg}</div>;| ^^^^^^11 | }12 | }13 |ok, 可以看到,直接報錯了,說“Type ‘1’ is not assignable to type ‘string’.” 這說明我們的配置起作用了,同時還報了一些錯誤,說找不到“JSX.IntrinsicElements”類型。
ok! 我們直接在src目錄底下定義一個shims-tsx.d.ts文件,然后用命名空間形式去聲明一個JSX模塊,
shims-tsx.d.ts:
import Vue, { VNode } from "vue";declare global {namespace JSX {// tslint:disable no-empty-interfaceinterface Element extends VNode {}// tslint:disable no-empty-interfaceinterface ElementClass extends Vue {}interface IntrinsicElements {[elem: string]: any;}} }再次運行會發(fā)現沒有報錯了。
optimization
生產環(huán)境的代碼壓縮并且去掉所有注釋
config.when(!isDev,()=>{config.optimization.minimize(true).minimizer("terser").use(require("terser-webpack-plugin"),[{extractComments: false, //去除注釋terserOptions:{output: {comments: false //去除注釋}}}]); },()=>{});devtool開發(fā)改成“eval-cheap-module-source-map”加快速度
config.when(!isDev,()=>{config.optimization.minimize(true).minimizer("terser").use(require("terser-webpack-plugin"),[{extractComments: false, //去除注釋terserOptions:{output: {comments: false //去除注釋}}}]); },()=>{config.devtool("eval-cheap-module-source-map"); });分包機制優(yōu)化
config.optimization.splitChunks({cacheGroups: {vendors: { //分離入口文件引用node_modules的module(vue、@babel/xxx)name: `chunk-vendors`,test: /[\\/]node_modules[\\/]/,priority: -10,chunks: 'initial'},common: { //分離入口文件引用次數>=2的modulename: `chunk-common`,minChunks: 2,priority: -20,chunks: 'initial',reuseExistingChunk: true}}}).runtimeChunk("single"); //分離webpack的一些幫助函數,比如webpackJSONP等等同樣,html-webpack-plugin插件也需要修改一下:
.plugin("html")// 添加html-webpack-plugin插件.use(require("html-webpack-plugin"),[{template: path.resolve(__dirname,"./public/index.html"), //指定模版文件chunks: ["runtime", "chunk-vendors", "chunk-common", "app"], //指定需要加載的chunksinject: "body" //指定script腳本注入的位置為body}]).end()webpack全局配置,webpack.config.js:
const path = require("path"); const config = new (require("webpack-chain"))(); const isDev = !!process.env.WEBPACK_DEV_SERVER; config.context(path.resolve(__dirname, ".")) //webpack上下文目錄為項目根目錄.entry("app") //入口文件名稱為app.add("./src/main.ts") //入口文件為./src/main.ts.end().output.path(path.join(__dirname,"./dist")) //webpack輸出的目錄為根目錄的dist目錄.filename("[name].[hash:8].js").end().resolve.extensions.add(".js").add(".jsx").add(".ts").add(".tsx").add(".vue") //配置以.js等結尾的文件當模塊使用的時候都可以省略后綴.end().end().module.rule('js').test(/\.m?jsx?$/) //對mjs、mjsx、js、jsx文件進行babel配置.exclude.add(filepath => {// Don't transpile node_modulesreturn /node_modules/.test(filepath)}).end().use("babel-loader").loader("babel-loader").end().end().rule("type-script").test(/\.tsx?$/) //loader加載的條件是ts或tsx后綴的文件.use("babel-loader").loader("babel-loader").end().use("ts-loader").loader("ts-loader").options({ //ts-loader相關配置transpileOnly: true, // disable type checker - we will use it in fork pluginappendTsSuffixTo: ['\\.vue$']}).end().end().rule("vue").test(/\.vue$/)// 匹配.vue文件.use("vue-loader").loader("vue-loader").end().end().rule("sass").test( /\.(sass|scss)$/)//sass和scss文件.use("extract-loader")//提取css樣式到單獨css文件.loader(require('mini-css-extract-plugin').loader).options({hmr: isDev //開發(fā)環(huán)境開啟熱載}).end().use("css-loader")//加載css模塊.loader("css-loader").end().use("postcss-loader")//處理css樣式.loader("postcss-loader").options( {config: {path: path.resolve(__dirname, "./postcss.config.js")}}).end().use("sass-loader")//sass語法轉css語法.loader("sass-loader").end().end().rule('eslint')//添加eslint-loader.exclude.add(/node_modules/)//校驗的文件除node_modules以外.end().test(/\.(vue|(j|t)sx?)$/)//加載.vue、.js、.jsx、.ts、.tsx文件.use('eslint-loader').loader(require.resolve('eslint-loader')).options({emitWarning: true, //把eslint報錯當成webpack警告emitError: !isDev, //把eslint報錯當成webapck的錯誤}).end().end().end().plugin("vue-loader-plugin")//vue-loader必須要添加vue-loader-plugin.use(require("vue-loader").VueLoaderPlugin,[]).end().plugin("html")// 添加html-webpack-plugin插件.use(require("html-webpack-plugin"),[{template: path.resolve(__dirname,"./public/index.html"), //指定模版文件chunks: ["runtime", "chunk-vendors", "chunk-common", "app"], //指定需要加載的chunksinject: "body" //指定script腳本注入的位置為body}]).end().plugin("extract-css")//提取css樣式到單獨css文件.use(require('mini-css-extract-plugin'), [{filename: "css/[name].css",chunkFilename: "css/[name].css"}]).end().plugin('fork-ts-checker') //配置fork-ts-checker.use(require('fork-ts-checker-webpack-plugin'), [{eslint: {files: './src/**/*.{ts,tsx,js,jsx,vue}' // required - same as command `eslint ./src/**/*.{ts,tsx,js,jsx} --ext .ts,.tsx,.js,.jsx`},typescript: {extensions: {vue: {enabled: true,compiler: "vue-template-compiler"},}}}]).end().devServer.host("0.0.0.0") //為了讓外部服務訪問.port(8090) //當前端口號.hot(true) //熱載.open(true) //開啟頁面.overlay({warnings: true,errors: true}) //webpack錯誤和警告信息顯示到頁面 config.when(!isDev,()=>{config.optimization.minimize(true).minimizer("terser").use(require("terser-webpack-plugin"),[{extractComments: false, //去除注釋terserOptions:{output: {comments: false //去除注釋}}}]); },()=>{config.devtool("eval-cheap-module-source-map"); }); config.optimization.splitChunks({cacheGroups: {vendors: { //分離入口文件引用node_modules的module(vue、@babel/xxx)name: `chunk-vendors`,test: /[\\/]node_modules[\\/]/,priority: -10,chunks: 'initial'},common: { //分離入口文件引用次數>=2的modulename: `chunk-common`,minChunks: 2,priority: -20,chunks: 'initial',reuseExistingChunk: true}}}).runtimeChunk("single"); //分離webpack的一些幫助函數,比如webpackJSONP等等module.exports = config.toConfig();總結
ok,到這我們的vue實戰(zhàn)算是結束了,大家也可以直接把demo用到項目中,沒毛病!!我們花了兩節(jié)來實戰(zhàn)了一個vue項目,有小伙伴有疑問了:“這么多內容我們怎么記得住呢?” 是的! 那么多知識點我也是記不住的,但是這個是真沒辦法的,你需要時刻關注一些更新動態(tài)或者社區(qū)大佬分享的一些經驗了,不過說到底,只有你自己明白你需要的是什么就夠了,不要自己都懵逼了那就完了,建議初級剛接觸前端的童鞋可以直接用官方的腳手架生成項目,但是對于中級和高級的同學那就需要自己掌握每一個知識點了,要有脫離腳手架也能擼一個項目的功底!!
demo鏈接地址:https://github.com/913453448/webpack-vue-demo.git
好啦~~ 本節(jié)到此就結束了,未完待續(xù)!!
總結
以上是生活随笔為你收集整理的手把手教你从0开始搭建一个vue项目(完结)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何删除右键“新建”菜单中的不需要的菜单
- 下一篇: ProE 工程图教程系列-3 Pro/E