从零搭建一个vue2+webpack4的项目结构

一、我为什么不使用vue-cli脚手架?

众所周知,vue相对来说,比较容易上手,搭建一个工程,一行npm install vue-cli -g能帮我们快速搭建一个项目架构,我们不用去想webpack怎么设计,vue-loader是做什么用的等等,因为vue-cli已经帮助我们搭建了最基本的开发环境,我们装好了cli,就可以直接进入代码的开发阶段。

当然,单纯的使用cli,也能开发出优秀的项目,但是对于后续项目的多样化,一些优秀的插件的不断更新,使我们不能更好的迭代和更新我们的项目,对vue的理解和项目统筹的能力都没有更大的提高,比如现在让你webpack2升级到webpack4,你会不会一脸懵逼?

所以在看了一些文字,和对公司项目的理解下,搭建了一套vue+webpack的框架,既满足SPA的开发,也能够满足一些构建单独网页的多入口项目框架,当然这只满基本需要的1.0版本,更多的润色与拆分,留着各位自行扩展吧。

二、初级构建

新建一个some文件夹,打开终端开始构建

npm init

安装框架所需基本:

npm i webpack vue vue-loader -D (vue-loader:解析和转换 .vue 文件,提取出其中的逻辑代码 script、样式代码 style、以及 HTML 模版 template,再分别把它们交给对应的 Loader 去处理)

npm i webpack-cli -s -d (webpack4 已经开始使用webpack-cli)

npm i css-loader vue-template-compiler -D
(css-loader:加载由 vue-loader 提取出的 CSS 代码。 )
(vue-template-compiler:把 vue-loader 提取出的 HTML 模版编译成对应的可执行的 JavaScript 代码)

然后我们在src目录下面新建一个app.vue文件,里面就可以写一些关于项目的业务代码:

<template>
    <div> luckfine </div>
</template>
<script>
    export default {
        data () {
            //text: 'luckfine'
        }
    }
</script>
<style>
</style>

后缀为.vue 文件是不可以在浏览器里直接运行的,我们得让它运行起来。

现在我们要在项目根目录下新建一个webpack.config.js文件,webpack(号称打包一切)它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用。

我们在src目录下新建一个index.js作为入口文件,顺便在里面写点东西:

// index.js
import Vue from 'vue'
import App from './app.vue'

const root = document.createElement('div')
document.body.appendChild(root)

new Vue({
    render: (h) => h(App)
}).$mount(root)

index.js准备完毕之后,那么在webpack.config.js里面就可以这样写:

// webpack.config.js
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
    entry:  path.join(__dirname, 'src/index.js'),
    mode:'develop',
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /.vue$/,
                loader: 'vue-loader'
            }
        ]
    },
    plugins:[
        new VueLoaderPlugin()
    ],
}

webpack把所有的文件打包成一个bundle.js文件,并且是能在浏览器里面直接运行的代码。现在我们可以在package.json 文件里的scripts对象里面添加一个脚本:
(注意:webpack4中已经在启动时用mode指定启动环境)

 "scripts": {
    "build": "webpack --config webpack.config.js --mode=development"
  },

添加完这段之后,我们再去terminal执行下npm run build

会发现build成功

image.png

三,深入扩展(SPA)

现在,我们已经能够用浏览器解析vue文件,但是搭建一个项目框架岂是这么简单的?现在开始我们的spa构建之路。

删掉原来的app.vue 和 index.js

在src 新建一个components,用作我们存放vue文件

在src 新建一个文件夹h5,用作项目一(SPA),里面的pages里面放的就是我们的入口文件的js

image.png

因为入口文件已经更改,此时需要更改webpack.config.js

entry:  path.join(__dirname, 'src/h5/pages/demo.js'),

为了本地能启动我们的项目,我们可以安装一个devServernpm install webpack-dev-server -s -d,和html-webpack-pluginnpm install html-webpack-plugin -d -s
此时的webpack.config.js

// webpack.config.js

const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry:  path.join(__dirname, 'src/h5/pages/demo.js'),
    mode:'develop',
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /.vue$/,
                loader: 'vue-loader'
            }
        ]
    },
    resolve: {
        extensions: [
          '.vue', '.js'
        ],
        modules: ["node_modules"],
        alias: {
          vue: 'vue/dist/vue.min.js',
          components: path.resolve(__dirname + '/src/components/'),
          '@': path.resolve('src')
        }
    },
    plugins:[
        new HtmlWebpackPlugin(),
        new VueLoaderPlugin()
    ],
    devServer: {
        historyApiFallback: {
          index: `/dist/h5/index.html`
        },
        host: '0.0.0.0',
        disableHostCheck: true
    }
}

package.json中添加启动入口"dev": "webpack-dev-server --inline --hot --mode=development",

此时我们在terminal执行npm run dev即可看到他自动给我们创建了一个index.html,把打包好的js插入进里面,且项目已经可以运行

image.png

为了丰富我们的项目,我们还需要项目中引入vuexnpm install vuex -s -d

在src目录下新建一个stores文件夹,里面放入一个getdata.js作为我们第一个获取数据的store

// getdata.js

/*
 * 同步形式的store,设置好state中的值即可
 * namespaced为true,是为了避免store的module之间,getters、mutations、actions发生命名冲突
 */

import $ from 'jquery'
export default {
  namespaced: true,
  state: {
    // 初始化时,务必要把所有的数据成员做初始化,否则后面数据的更新,将不会触发显示的更新
    dataList: []
  },
  mutations: {
    setData(state, res) {
      state.dataList = res;
    }
  },
  // 浏览器环境才可以使用actions来获取数据,服务端应该用Node.js的方式获取数据后,通过mutations同步的把数据存入到store
  actions: {
    getList({
      commit,
      rootState,
      state
    }, options) {
      return $.ajax({
        url: 'http://*******/getLastNotice'+‘公司接口,保密’,
        dataType: 'json'
      }).then(data => {
          commit('setData', data)
      })
    }
  }
}

在我们的入口文件中引入vuex,并把getdata作为我们一个stoe

// demo.js
import Vue from 'vue'
import Vuex from 'vuex'
import App from 'components/demo/demo'
import data from 'stores/getdata'

Vue.use(Vuex)

const store = new Vuex.Store({
    modules: {
        data
    }
  })

const root = document.createElement('div')
document.body.appendChild(root)

new Vue({
    render: (h) => h(App),
    store
}).$mount(root)

那么此时,我们就可以在组件里请求和获取这个store里面的数据

<style>

</style>
<template>
    <div id="test">{{vueData}}</div>
</template>
<script>
import {
    mapState
} from 'vuex'

    export default {
        data () {
            // text: 'demo'
        },
        computed:mapState({
            vueData: state => state.data.dataList
        }),
        mounted () {
            this.$store.dispatch('data/getList')
        }
    }
</script>

此时npm run dev运行可以看到在组件内,我们请求了这个接口,并且把这个接口数据渲染到了页面上

image.png

继续丰富,引入vue-routernpm install vue-router -d -s

为了简便,我就不创建那么多组件了,小伙伴可以自行优化

//  demo.js

import Vue from 'vue'
import Vuex from 'vuex'
import VueRouter from 'vue-router'
import App from 'components/demo/demo'
import data from 'stores/getdata'


Vue.use(Vuex)
Vue.use(VueRouter)

const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }

const routes = [
    { path: '/foo', component: Foo },
    { path: '/bar', component: Bar }
  ]

const router = new VueRouter({
    routes // short for `routes: routes`
  })

const store = new Vuex.Store({
    modules: {
        data
    }
  })

const root = document.createElement('div')
document.body.appendChild(root)

new Vue({
    render: (h) => h(App),
    store,
    router
}).$mount(root)

在组件中应用路由

<style>

</style>
<template>
    <div>
        <div id="test">{{vueData}}</div>
        <router-link to="/foo">Go to Foo</router-link>
        <router-link to="/bar">Go to Bar</router-link>
        <router-view></router-view>
    </div>
</template>
<script>
import {
    mapState
} from 'vuex'

    export default {
        data () {
            // text: 'demo'
        },
        computed:mapState({
            vueData: state => state.data.dataList
        }),
        mounted () {
            this.$store.dispatch('data/getList')
        }
    }
</script>

那么此时npm run dev,可以看到路由也被渲染出来了

image.png

四、深入扩展(多入口项目)

好了,到此刻为止,一个单页应用的vue架构就ok了,这时候,假如我们要做一个简单的宣传页,那么我们新建一个路由,然后请求的时候把整个spa都 加载一遍吗?这样是不是太浪费资源了,那么我们在这个框架里,再新建一个文件夹,用来存放单入口文件,这样每次页面加载只这个宣传页的js,这样岂不是美哉。

那么我们知道,vue+webpack的项目基本都是基于node的,我们可以进入的项目,通过在启动时加入参数env='项目名称,入口文件名称',来获取我们将要启动的入口文件的js

小知识点,node有一个process对象,process是一个全局对象,在任何地方都能访问到它,通过这个对象提供的属性和方法,使我们可以对当前运行的程序的进程进行访问和控制,我们就通过process.argv来获取参数

在src下新建一个项目文件夹more-entry,在more-entry下建一个pages文件夹用于存放入口文件js,我们新建一个a.js 和 b.js ,

// a.js
import Vue from 'vue'
import Vuex from 'vuex'
import App from 'components/more-entry/a'
import data from 'stores/getdata'


Vue.use(Vuex)


const store = new Vuex.Store({
    modules: {
        data
    }
  })

const root = document.createElement('div')
document.body.appendChild(root)

new Vue({
    render: (h) => h(App),
    store
}).$mount(root)
// b.js
import Vue from 'vue'
import Vuex from 'vuex'
import App from 'components/more-entry/b'
import data from 'stores/getdata'


Vue.use(Vuex)


const store = new Vuex.Store({
    modules: {
        data
    }
  })

const root = document.createElement('div')
document.body.appendChild(root)

new Vue({
    render: (h) => h(App),
    store
}).$mount(root)

那么对应的组件为

// a.vue
<style>

</style>
<template>
    <div>
        <h3>a组件</h3>
        <div id="test">{{vueData}}</div>
    </div>
</template>
<script>
import {
    mapState
} from 'vuex'

    export default {
        data () {
            // text: 'demo'
        },
        computed:mapState({
            vueData: state => state.data.dataList
        }),
        mounted () {
            this.$store.dispatch('data/getList')
        }
    }
</script>
// b.vue
<style>

</style>
<template>
    <div>
        <h3>b组件</h3>
        <div id="test">{{vueData}}</div>
    </div>
</template>
<script>
import {
    mapState
} from 'vuex'

    export default {
        data () {
            // text: 'demo'
        },
        computed:mapState({
            vueData: state => state.data.dataList
        }),
        mounted () {
            this.$store.dispatch('data/getList')
        }
    }
</script>

结合我们的spa项目,此时的webpack.config.js

// webpack.config.js
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const fs = require('fs');

let featureName = 'h5';  // 要启动的项目名称
let page = null; // 启动项目的入口文件js名称
    process.argv.some(function(arg) {
        console.log(arg)
        let arr = arg.match(/\-\-env=([a-zA-Z0-9\-_,]+)/);
        if (arr) {
            let config = arr[1].split(',');

            featureName = config[0];
            if (config[1]) {
                page = config[1];
            }
        }
    });

module.exports = {
    entry:  path.join(__dirname, 'src/'+featureName+'/pages/'+page+'.js'),
    mode:'develop',
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist'),
        filename:featureName + '/[name].js'
    },
    module: {
        rules: [
            {
                test: /.vue$/,
                loader: 'vue-loader'
            }
        ]
    },
    resolve: {
        extensions: [
          '.vue', '.js'
        ],
        modules: ["node_modules"],
        alias: {
          vue: 'vue/dist/vue.min.js',
          stores: __dirname + '/src/stores/',
          components: path.resolve(__dirname + '/src/components/'),
          '@': path.resolve('src')
        }
    },
    plugins:[
        new HtmlWebpackPlugin(),
        new VueLoaderPlugin()
    ],
    devServer: {
        // historyApiFallback: {
        //     index: `/dist/${featureName}/index.html`
        // },
        host: '0.0.0.0',
        disableHostCheck: true
    }
}

启动项目npm run dev -- --env=more-entry,b,那么可以看到浏览器中已经可以渲染出来

image.png

npm run dev -- --env=more-entry,a

image.png

npm run build -- --env=more-entry,b 可用来单独打包

image.png

到此为止,简单的项目架构就出来了,后续我们增加项目,只需要在src下建立对应的文件夹即可,增加常用工具js,引入渲染模板,增加对应loader,引入es-lint,解析es6的语法等等,各位可自行构建。

本文代码https://github.com/luckFine/vue-project.git

码字不易,欢迎打赏。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,780评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,424评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,397评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,576评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,997评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,945评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,107评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,850评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,625评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,804评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,285评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,613评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,291评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,164评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,963评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,096评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,886评论 2 278

推荐阅读更多精彩内容