用你的webpack實現vue-cli
本文圍繞前端工程化,用webpack從零搭建一個完整專案的過程
本文核心知識點:
- webpack的使用
- vue元件化思想
- Element-UI的使用
別走別走,迫不及待看結果了吧:
想學嗎,來俺教你(獻醜,哈哈)
實現步驟:
寫在前面:此案例用於強化你的webpack使用(須對webpack有一定的瞭解),若本文有錯誤以及執行出錯,歡迎隨時來擾我這隻:愛學習的小白
- 建立專案資料夾(vue_todo)
- 生成專案描述檔案(npm init -y)
- 在專案根目錄下建立index.html(程式碼後補)
- 在專案根目錄下建立src資料夾(新增main.js和App.vue)
- main.js(涵蓋全文)
//! 需要匯入例項化vue根例項
import Vue from 'vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';
Vue.use(ElementUI);
new Vue({
el: '#app',
components: {
App
},
template: '<App/>'
});
- 專案根目錄下建立build資料夾(新增webpack配置檔案),此處用於更好的區分開發環境和生產環境;也可不建,直接在根目錄下建立webpack.config.js(名字唯一)
生成如下:
- 安裝webpack,webpack-cli
- 在build資料夾下建立webpack.dev.js(進行webpack的配置),此處需瞭解webpack的基本使用webpack基本使用,一般配置有(entry:入口,output:出口,loader:載入器,plugins:外掛)
本專案配置為:
寫在前面:此處生產環境與開發環境程式碼冗餘度過高,所以抽出公共部分(webpack.base.js)並用第三方模組(webpack-merge)進行合併,此處loader以及plugin的使用隨著官網的更新,可能會發生變化,以官網為準,webpack官網
- webpack.base.js(註釋含細節,其中路徑相關項切記與自己檔案路徑保持一致)
// 引入路徑處理模組
const path = require('path');
// 引入vue-loader外掛(載入vue模板)
const VueloaderPlugin = require('vue-loader/lib/plugin');
// 引入html-webpack-plugin(生成預覽的html頁)
const HtmlWepackPlugin = require('html-webpack-plugin');
// 引入clean-wenpack-plugin(需進行解構)(每次打包前刪除之前dist目錄下的js檔案)
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
// 打包入口
entry: './src/main.js',
// 打包出口
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '../dist')
},
// 配置打包規則
module: {
rules: [{
test: /\.vue$/,
use: 'vue-loader'
}, {
test: /\.(jpg|png|gif|bmp|eot|svg|woff|woff2|ttf)$/,
use: [{
loader: 'url-loader',
query: {
name: '[name].[ext]'
}
}]
}, {
test: /\.css$/,
use: ['style-loader', 'css-loader']
}, {
test: /\.styl(us)?$/,
use: ['vue-style-loader', 'css-loader', 'postcss-loader', 'stylus-loader']
}, {
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}]
},
// 外掛
plugins: [
new VueloaderPlugin(),
new HtmlWepackPlugin({
template: './index.html'
}),
new CleanWebpackPlugin()
],
resolve: {
alias: {
'vue': 'vue/dist/vue.js'
}
},
performance: {
hints: false
}
}
- webpack.dev.js
const baseConfig = require('./webpack.base.js');
const merge = require('webpack-merge');
// 引入webpack物件(實現熱模組替換)
const webpack = require('webpack');
const devConfig = {
mode: 'development',
// devServer配置
devServer: {
contentBase: '../dist',
// 打包完成自動開啟
open: true,
// 模組熱替換
hot: true
},
//! 使打包後的js檔案與原始檔產生對映關係(增加糾錯速度,錯誤定位),官網介紹詳細
//! eval一般應用於生產環境(production)
//! devtool: 'eval',
//vue腳手架一般配置(打包速度較慢)
devtool: 'cheap-module-eval-source-map',
// 外掛
plugins: [
new webpack.HotModuleReplacementPlugin()
],
}
module.exports = merge(baseConfig, devConfig);
- webpack.prod.js
const baseConfig = require('./webpack.base.js');
const merge = require('webpack-merge');
const prodConfig = {
mode: 'production'
}
module.exports = merge(baseConfig, prodConfig)
注意:此處的loader(postcss-loader,babel-loader)需要新增額外配置檔案
passcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
.babelrc
{
"presets": ["@babel/preset-env"]
}
- 在專案描述(package.js)中寫入指令碼(script標籤中寫),用於打包專案(此處涵蓋所有依賴項)
{
"name": "vue_todo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.9.6",
"@babel/preset-env": "^7.9.6",
"autoprefixer": "^9.7.6",
"babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.5.3",
"file-loader": "^6.0.0",
"html-webpack-plugin": "^4.3.0",
"postcss-loader": "^3.0.0",
"style-loader": "^1.2.1",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"url-loader": "^4.1.0",
"vue-loader": "^15.9.2",
"vue-template-compiler": "^2.6.11",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^4.2.2"
},
"dependencies": {
"element-ui": "^2.13.1",
"vue": "^2.6.11"
}
}
配置完成,安心擼vue元件
寫在前面:對於元件的認識,可以學習:vue元件化開發
- 對專案介面進行元件拆分:App(MainHeader,MainTodo(TodoItem,TodoInfo),MainFooter)
- 進行每個元件的編寫(注意元件之間的資料傳遞)
- 進行元件樣式的編寫(此處除css樣式外,還演示了stylus樣式(未涉及可忽略))
- 進行子元件的掛載
- 在元件中使用Element-UI
奉上我的vue程式碼:
- App.vue
<template>
<div>
<!-- 呼叫元件時必須用短橫線的方式寫入標籤 -->
<main-header></main-header>
<main-todo></main-todo>
<main-footer></main-footer>
</div>
</template>
<script>
//! 引入樣式,格式為stylus
import './assets/styles/global.styl'
//! 匯入子元件(頭部)
import MainHeader from './components/MainHeader.vue'
//! 匯入子元件(中間部分)
import MainTodo from './components/MainTodo/MainTodo.vue'
//! 匯入子元件(底部)
import MainFooter from './components/MainFooter.vue'
export default {
name:'App',
components:{
//! 元件名:元件物件(es6中鍵和值同名,可簡寫)
MainHeader:MainHeader,
MainTodo:MainTodo,
MainFooter:MainFooter
}
}
</script>
<style lang="css" scoped>
</style>
- MainHeader.vue
<template>
<header>
<h1>ToDoList</h1>
</header>
</template>
<script>
export default {
//todo 元件名儘量與檔名一致(思路容易捋順)
name:'MainHeader'
}
</script>
<style lang="stylus" scoped>
header {
height 200px;
width 100%;
text-align center;
}
header h1{
line-height 200px;
font-size 100px;
font-weight 100;
color pink;
text-shadow 1px 1px 1px pink,3px 3px 9px red;
}
</style>
- MainTodo
<template>
<div class="main-todo">
<input type="text" class="what-todo" placeholder="What do you want to do ?" autofocus @keyup.enter="addTodo" v-model="content">
<todo-item v-for="(item,index) in filterData" :key="index" :todo="item" @delTodo="delThis($event)"></todo-item>
<todo-info :unComplete="unComplete" :AllComplete="AllComplete" @toggleState="handleToggleState($event)" @delComplete="delComplete"></todo-info>
</div>
</template>
<script>
//todo 匯入TodoItem子元件
import TodoItem from './coms/TodoItem.vue'
//todo 匯入TodoInfo子外掛
import TodoInfo from './coms/TodoInfo.vue'
//todo 用id自加模擬計數器
let id = 0
export default {
name:'MainTodo',
data(){
return{
todos:[],
content:'',
filter:'all'
}
},
methods:{
addTodo(){
//todo 如果輸入框為空,直接返回
if(this.content === '') return
this.todos.unshift({
id:id++,
content:this.content,
complete:true
})
this.content = ''
},
delThis(id){
const index = this.todos.findIndex(item=>{
return item.id == id
})
this.todos.splice(index,1)
},
handleToggleState(state){
this.filter = state
},
delComplete(){
this.todos = this.todos.filter(item=>item.complete == true)
}
},
computed:{
unComplete(){
const unDone = this.todos.filter(item=>{
return item.complete == true
})
return unDone.length
},
AllComplete(){
const Done = this.todos.filter(item=>{
return item.complete == false
})
return Done.length
},
filterData(){
switch(this.filter){
case 'all':
return this.todos
break;
case 'active':
return this.todos.filter(item=>item.complete == true)
break;
case 'complete':
return this.todos.filter(item=>item.complete == false)
break;
}
}
},
components:{
TodoItem,
TodoInfo,
}
}
</script>
<style lang="stylus" scoped>
.main-todo{
width 600px
background-color rgba(0,0,0,.2)
border-radius 18px 18px 0 0
overflow hidden
margin 0 auto
box-sizing border-box
}
.what-todo{
height 45px
width 100%
font-size 20px
font-weight 300
padding-left 16px
border 0
outline 0
background-color rgba(0,0,0,.2)
border-bottom 3px dashed pink
}
::placeholder{
color #ccc
}
</style>
- MainTodo子元件TodoItem.vue
<template>
<div class="box">
<span class="switch"><el-switch v-model="todo.complete" active-color="#13ce66" inactive-color="#ff4949"></el-switch></span>
<label :class="['todo',todo.complete?'':'complete']">{{todo.content}}</label>
<button @click="$emit('delTodo',todo.id)"><i class="el-icon-delete active"></i></button>
</div>
</template>
<script>
export default {
name:'TodoItem',
props:['todo'],
data(){
return{
value:true
}
}
}
</script>
<style lang="stylus" scoped>
.box{
width 600px
height 40px
display flex
justify-content space-between
border-top 1px dashed pink
}
.switch{
padding 0px 9px
}
.box span,
.active{
height 40px
width 40px
text-align center
line-height 40px
}
.active{
color red
font-size 20px
cursor pointer
}
.todo{
flex 1
height 100%
font-size 20px
color red
line-height 40px
padding-left 15px
}
.complete{
color #ccc
text-decoration line-through
}
button{
outline none
border none
background-color rgba(0,0,0,0)
}
</style>
- MainTodo子元件TodoInfo
<template>
<div class="todo-info">
<span class="total" v-if="(state == 'all' | state == 'active')">{{unComplete}} item left</span>
<span class="total" v-if="(state == 'complete')">{{AllComplete}} item left</span>
<div class="tabs">
<a :class="state == item ? 'active': ''" v-for="(item,index) in states" :key="index" @click="toggleState(item)">{{item}}</a>
</div>
<el-button type="success" @click="$emit('delComplete')">Clear Complete</el-button>
</div>
</template>
<script>
export default {
name:'TodoInfo',
props:['unComplete','AllComplete'],
data(){
return{
states:['all','active','complete'],
state:'all'
}
},
methods:{
toggleState(state){
this.state = state
this.$emit('toggleState',state)
}
}
}
</script>
<style lang="css" scoped>
.todo-info{
display: flex;
justify-content: space-between;
padding: 5px 10px;
font-weight: 400;
line-height: 30px;
}
.total{
padding: 5px 0px;
color: red;
font-size: 16px;
font-weight: 700;
}
.tabs{
display: flex;
justify-content: space-between;
width: 200px;
}
.tabs a{
border: 1px solid palevioletred;
padding: 5px 10px;
border-radius: 12px;
cursor: pointer;
}
.active{
background-color: hotpink;
color: #fff;
}
</style>
- MainFooter
<template>
<footer>Written By A Seeker Of Knowladge</footer>
</template>
<script>
export default {
name:'MainFooter'
}
</script>
<style lang="css" scoped>
footer{
font-style: 24px;
font-weight: 800;
margin-top: 20px;
text-align: center;
color: pink;
text-shadow: 1px 1px 1px black,3px 3px 9px hotpink;
}
</style>
快在終端輸入:npm run dev 跑起來吧
Last:
有小夥伴問我的怪癖,註釋後為撒要寫('!','?','todo','cwen'),今天統回覆下,這是vscode的註釋高亮外掛。可以讓你的註釋也能像程式碼一樣招人喜歡。最後附上外掛名:Better Comments
- 安裝外掛
- 在settings.json裡自定義自己的樣式
- 預覽效果
身體and靈魂必須一個在路上(共勉)