用專案強化你的webpack

CW發表於2020-05-17

用你的webpack實現vue-cli

本文圍繞前端工程化,用webpack從零搭建一個完整專案的過程

本文核心知識點:

  1. webpack的使用
  2. vue元件化思想
  3. Element-UI的使用
別走別走,迫不及待看結果了吧:

在這裡插入圖片描述

想學嗎,來俺教你(獻醜,哈哈)

實現步驟:

寫在前面:此案例用於強化你的webpack使用(須對webpack有一定的瞭解),若本文有錯誤以及執行出錯,歡迎隨時來擾我這隻:愛學習的小白

  1. 建立專案資料夾(vue_todo)
  2. 生成專案描述檔案(npm init -y)
  3. 在專案根目錄下建立index.html(程式碼後補)
  4. 在專案根目錄下建立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/>'
});
  1. 專案根目錄下建立build資料夾(新增webpack配置檔案),此處用於更好的區分開發環境和生產環境;也可不建,直接在根目錄下建立webpack.config.js(名字唯一)
    生成如下:
    在這裡插入圖片描述
  2. 安裝webpack,webpack-cli
  3. 在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"]
}
  1. 在專案描述(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元件化開發

  1. 對專案介面進行元件拆分:App(MainHeader,MainTodo(TodoItem,TodoInfo),MainFooter)
  2. 進行每個元件的編寫(注意元件之間的資料傳遞)
  3. 進行元件樣式的編寫(此處除css樣式外,還演示了stylus樣式(未涉及可忽略))
  4. 進行子元件的掛載
  5. 在元件中使用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>
  1. 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>
  1. 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

  1. 安裝外掛
  2. 在settings.json裡自定義自己的樣式
  3. 預覽效果
    在這裡插入圖片描述

身體and靈魂必須一個在路上(共勉)

相關文章