簡單vue項目腳手架

簡單vue項目腳手架

github地址

使用技術棧

  • webpack(^2.6.1)javascript

  • webpack-dev-server(^2.4.5)css

  • vue(^2.3.3)html

  • vuex(^2.3.1)vue

  • vue-router(^2.5.3)java

  • vue-loader(^12.2.1)node

  • eslint(^3.19.0)webpack

須要學習的知識

vue.js
vuex
vue-router
vue-loader
webpack2
eslint
內容至關多,尤爲是webpack2教程,官方腳手架vue-cli雖然至關完整齊全,可是修改起來仍是挺花時間,因而本身參照網上的資料和以前作過的項目用到的構建工具地去寫了一個簡單vue項目腳手架。適用於多頁面spa模式的業務場景(每一個模塊都是一個spa)。比較簡單,主要就是一個webpack.config.js文件,沒有說特地地去劃分紅分webpack.dev.config.js、webpack.prov.config.js等等。下面是整個webpack.config.js文件代碼:git

const { resolve } = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const glob = require('glob')

module.exports = (options = {}) => {
    // 配置文件,根據 run script不一樣的config參數來調用不一樣config
    const config = require('./config/' + (process.env.npm_config_config || options.config || 'dev'))
    // 遍歷入口文件,這裏入口文件與模板文件名字保持一致,保證能同時合成HtmlWebpackPlugin數組和入口文件數組
    const entries = glob.sync('./src/modules/*.js')
    const entryJsList = {}
    const entryHtmlList = []
    for (const path of entries) {
        const chunkName = path.slice('./src/modules/'.length, -'.js'.length)
        entryJsList[chunkName] = path
        entryHtmlList.push(new HtmlWebpackPlugin({
            template: path.replace('.js', '.html'),
            filename: 'modules/' + chunkName + '.html',
            chunks: ['manifest', 'vendor', chunkName]
        }))
    }
    // 處理開發環境和生產環境ExtractTextPlugin的使用狀況
    function cssLoaders(loader, opt) {
        const loaders = loader.split('!')
        const opts = opt || {}
        if (options.dev) {
            if (opts.extract) {
                return loader
            } else {
                return loaders
            }
        } else {
            const fallbackLoader = loaders.shift()
            return ExtractTextPlugin.extract({
                use: loaders,
                fallback: fallbackLoader
            })
        }
    }

    const webpackObj = {
        entry: Object.assign({
            vendor: ['vue', 'vuex', 'vue-router']
        }, entryJsList),
        // 文件內容生成哈希值chunkhash,使用hash會更新全部文件
        output: {
            path: resolve(__dirname, 'dist'),
            filename: options.dev ? 'static/js/[name].js' : 'static/js/[name].[chunkhash].js',
            chunkFilename: 'static/js/[id].[chunkhash].js',
            publicPath: config.publicPath
        },

        externals: {

        },

        module: {
            rules: [
                // 只 lint 本地 *.vue 文件,須要安裝eslint-plugin-html,並配置eslintConfig(package.json)
                {
                    enforce: 'pre',
                    test: /.vue$/,
                    loader: 'eslint-loader',
                    exclude: /node_modules/
                },
                /*
                    http://blog.guowenfh.com/2016/08/07/ESLint-Rules/
                    http://eslint.cn/docs/user-guide/configuring
                    [eslint資料]
                 */
                {
                    test: /\.js$/,
                    exclude: /node_modules/,
                    use: ['babel-loader', 'eslint-loader']
                },
                // 須要安裝vue-template-compiler,否則編譯報錯
                {
                    test: /\.vue$/,
                    loader: 'vue-loader',
                    options: {
                        loaders: {
                            sass: cssLoaders('vue-style-loader!css-loader!sass-loader', { extract: true })
                        }
                    }
                },
                {
                    // 須要有相應的css-loader,由於第三方庫可能會有文件
                    // (如:element-ui) css在node_moudle
                    // 生產環境才須要code抽離,否則的話,會使熱重載失效
                    test: /\.css$/,
                    use: cssLoaders('style-loader!css-loader')
                },
                {
                    test: /\.(scss|sass)$/,
                    use: cssLoaders('style-loader!css-loader!sass-loader')
                },
                {
                    test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,
                    use: [
                        {
                            loader: 'url-loader',
                            options: {
                                limit: 10000,
                                name: 'static/imgs/[name].[ext]?[hash]'
                            }
                        }
                    ]
                }
            ]
        },

        plugins: [
            ...entryHtmlList,
            // 抽離css
            new ExtractTextPlugin({
                filename: 'static/css/[name].[chunkhash].css',
                allChunks: true
            }),
            // 抽離公共代碼
            new webpack.optimize.CommonsChunkPlugin({
                names: ['vendor', 'manifest']
            }),
            // 定義全局常量
            // cli命令行使用process.env.NODE_ENV不如指望效果,使用不了,因此須要使用DefinePlugin插件定義,定義形式'"development"'或JSON.stringify('development')
            new webpack.DefinePlugin({
                'process.env': {
                    NODE_ENV: options.dev ? JSON.stringify('development') : JSON.stringify('production')
                }
            })

        ],

        resolve: {
            // require時省略的擴展名,再也不須要強制轉入一個空字符串,如:require('module') 不須要module.js
            extensions: ['.js', '.json', '.vue', '.scss', '.css'],
            // require路徑簡化
            alias: {
                '~': resolve(__dirname, 'src'),
                // Vue 最先會打包生成三個文件,一個是 runtime only 的文件 vue.common.js,一個是 compiler only 的文件 compiler.js,一個是 runtime + compiler 的文件 vue.js。
                // vue.js = vue.common.js + compiler.js,默認package.json的main是指向vue.common.js,而template 屬性的使用必定要用compiler.js,所以須要在alias改變vue指向
                vue: 'vue/dist/vue'
            },
            // 指定import從哪一個目錄開始查找
            modules: [
                resolve(__dirname, 'src'),
                'node_modules'
            ]
        },
        // 開啓http服務,publicPath => 須要與Output保持一致 || proxy => 反向代理 || port => 端口號
        devServer: config.devServer ? {
            port: config.devServer.port,
            proxy: config.devServer.proxy,
            publicPath: config.publicPath,
            stats: { colors: true }
        } : undefined,
        // 屏蔽文件超過限制大小的warn
        performance: {
            hints: options.dev ? false : 'warning'
        },
        // 生成devtool,保證在瀏覽器能夠看到源代碼,生產環境設爲false
        devtool: 'inline-source-map'
    }

    if (!options.dev) {
        webpackObj.devtool = false
        webpackObj.plugins = (webpackObj.plugins || []).concat([
            // 壓縮js
            new webpack.optimize.UglifyJsPlugin({
                // webpack2,默認爲true,能夠不用設置
                compress: {
                    warnings: false
                }
            }),
            //  壓縮 loaders
            new webpack.LoaderOptionsPlugin({
                minimize: true
            })
        ])
    }

    return webpackObj
}

上面的代碼對於每一個配置項都有註釋說明,這裏有幾點須要注意的:

1. webpack.config.js導出的是一個function

以前項目的webpack.config.js是以對象形式export的,以下es6

module.exports = {
    entry: ...,
    output: {
        ...
    },
    ...
}

而如今倒出來的是一個function,以下:github

module.exports = (options = {}) => { 
    return {
        entry: ...,
        output: {
            ...
        },
        ...
    }
}

這樣的話,function會在執行webpack CLI的時候獲取webpack的參數,經過options傳進function,看一下package.json:

"local": "npm run dev --config=local",
    "dev": "webpack-dev-server -d --hot --inline --env.dev --env.config dev",
    "build": "rimraf dist && webpack -p --env.config prod" //rimraf清空dist目錄

對於local命令,咱們執行的是dev命令,可是在最後面會--config=local,這是配置,這樣咱們能夠經過process.env.npm_config_config獲取到,而對於dev命令,對於--env XXX,咱們即可以在function獲取option.config= 'dev' 和 option.dev= true的值,特別方便!以此即可以同步參數來加載不一樣的配置文件了。對於-d-p不清楚的話,能夠這裏查看,很詳細!

// 配置文件,根據 run script不一樣的config參數來調用不一樣config
    const config = require('./config/' + (process.env.npm_config_config || options.config || 'dev'))

2. modules放置模板文件、入口文件、對應模塊的vue文件

將入口文件和模板文件放到modules目錄(名字保持一致),webpack文件會經過glob讀取modules目錄,遍歷生成入口文件對象和模板文件數組,以下:

const entries = glob.sync('./src/modules/*.js')
    const entryJsList = {}
    const entryHtmlList = []
    for (const path of entries) {
        const chunkName = path.slice('./src/modules/'.length, -'.js'.length)
        entryJsList[chunkName] = path
        entryHtmlList.push(new HtmlWebpackPlugin({
            template: path.replace('.js', '.html'),
            filename: 'modules/' + chunkName + '.html',
            chunks: ['manifest', 'vendor', chunkName]
        }))
    }

對於HtmlWebpackPlugin插件中幾個配置項的意思是,template:模板路徑,filename:文件名稱,這裏爲了區分開來模板文件我是放置在dist/modules文件夾中,而對應的編譯打包好的js、img(對於圖片咱們是使用file-loader、url-loader進行抽離,對於這兩個不是很理解的,能夠看這裏)、css我也是會放在dist/下對應目錄的,這樣目錄會比較清晰。chunks:指定插入文件中的chunk,後面咱們會生成manifest文件、公共vendor、以及對應生成的jscss(名稱同樣)

3. 處理開發環境和生產環境ExtractTextPlugin的使用狀況

開發環境,不須要把css進行抽離,要以style插入html文件中,能夠很好實現熱替換
生產環境,須要把css進行抽離合並,以下(根據options.dev區分開發和生產):

// 處理開發環境和生產環境ExtractTextPlugin的使用狀況
    function cssLoaders(loader, opt) {
        const loaders = loader.split('!')
        const opts = opt || {}
        if (options.dev) {
            if (opts.extract) {
                return loader
            } else {
                return loaders
            }
        } else {
            const fallbackLoader = loaders.shift()
            return ExtractTextPlugin.extract({
                use: loaders,
                fallback: fallbackLoader
            })
        }
    }
    ...
    // 使用狀況
    // 注意:須要安裝vue-template-compiler,否則編譯會報錯
    {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
            loaders: {
                sass: cssLoaders('vue-style-loader!css-loader!sass-loader', { extract: true })
            }
        }
    },
    ...
    {
        test: /\.(scss|sass)$/,
        use: cssLoaders('style-loader!css-loader!sass-loader')
    }

再使用ExtractTextPlugin合併抽離到static/css/目錄

4. 定義全局常量

cli命令行(webpack -p)使用process.env.NODE_ENV不如指望效果,使用不了,因此須要使用DefinePlugin插件定義,定義形式'"development"'或JSON.stringify(process.env.NODE_ENV),我使用這樣的寫法'development',結果報錯(針對webpack2),查找了一下網上資料,是這樣講的,能夠去看一下,設置以下:

new webpack.DefinePlugin({
        'process.env': {
            NODE_ENV: options.dev ? JSON.stringify('development') : JSON.stringify('production')
        }
    })

5. 使用eslint修正代碼規範

經過eslint來檢查代碼的規範性,經過定義一套配置項,來規範代碼,這樣多人協做,寫出來的代碼也會比較優雅,很差的地方是,就是配置項太多,有些默認項設置咱們不須要,可是確是到處限制咱們,須要經過配置屏蔽掉,能夠經過.eslintrc 文件或是package.json的eslintConfig,還有其餘方式,能夠到中文網看,這裏我用的是package.json方式,以下:

...
  "eslintConfig": {
    "parser": "babel-eslint",
    "extends": "enough",
    "env": {
      "browser": true,
      "node": true,
      "commonjs": true,
      "es6": true
    },
    "rules": {
      "linebreak-style": 0,
      "indent": [2, 4],
      "no-unused-vars": 0,
      "no-console": 0
    },
    "plugins": [
      "html"
    ]
  },
  ...

咱們還須要安裝 npm install eslint eslint-config-enough eslint-loader --save-dev,eslint-config-enough是所謂的配置文件,這樣package.json的內容才能起效,可是不當當是這樣,對應編輯器也須要安裝對應的插件,sublime text 3須要安裝SublimeLinter、SublimeLinter-contrib-eslint插件。對於全部規則的詳解,能夠去看官網,也能夠去這裏看,很詳細!
因爲咱們使用的是vue-loader,天然咱們是但願能對.vue文件eslint,那麼須要安裝eslint-plugin-html,在package.json中進行配置。而後對應webpack配置:

{
        enforce: 'pre',
        test: /.vue$/,
        loader: 'eslint-loader',
        exclude: /node_modules/
    }

咱們會發現webpack v1和v2之間會有一些不一樣,好比webpack1對於預先加載器處理的執行是這樣的,

module: {
    preLoaders: [
      {
        test: /\.js$/,
        loader: "eslint-loader"
      }
    ]
  }

更多的不一樣能夠到中文網看,很詳細,不作拓展。

6. alias vue指向問題

...
    alias: {
        vue: 'vue/dist/vue'
    },
    ...

Vue 最先會打包生成三個文件,一個是 runtime only 的文件 vue.common.js,一個是 compiler only 的文件 compiler.js,一個是 runtime + compiler 的文件 vue.js。
vue.js = vue.common.js + compiler.js,默認package.json的main是指向vue.common.js,而template 屬性的使用必定要用compiler.js,所以須要在alias改變vue指向

7. devServer的使用

以前的項目中使用的是用express啓動http服務,webpack-dev-middleware+webpack-hot-middleware,這裏會用到compiler+compilation,這個是webpack的編譯器和編譯過程的一些知識,也不是很懂,後續要去作作功課,應該能夠加深對webpack運行機制的理解。這樣作的話,感受複雜不少,對於webpack2.0 devServer彷佛功能更強大更加完善了,因此直接使用就能夠了。以下:

devServer: {
        port: 8080, //端口號
        proxy: { //方向代理 /api/auth/ => http://api.example.dev
            '/api/auth/': {
                target: 'http://api.example.dev',
                changeOrigin: true,
                pathRewrite: { '^/api': '' }
            }
        },
        publicPath: config.publicPath,
        stats: { colors: true }
    }
    //changeOrigin會修改HTTP請求頭中的Host爲target的域名, 這裏會被改成api.example.dev
    //pathRewrite用來改寫URL, 這裏咱們把/api前綴去掉,直接使用/auth/請求

webpack 2 打包實戰講解得很是好,很是棒。能夠去看一下,必定會有所收穫!

8. 熱重載原理

webpack中文網,講的還算清楚,不過可能太笨,看起來仍是雲裏霧裏的,似懂非懂的,補補課,好好看看。

9. localtunnel的使用

Localtunnel 是一個可讓內網服務器暴露到公網上的開源項目,使用能夠看這裏

$ npm install -g localtunnel
$ lt --port 8080
your url is: https://uhhzexcifv.localtunnel.me

這樣的話,能夠把咱們的本地網站暫時性地暴露到公網,能夠對網站作一些線上線下對比,詳細內容能夠去了解一下localtunnel,這裏講的是經過上面配置,訪問https://uhhzexcifv.localtunnel.me,沒有達到理想效果,出現了Invalid Host header的錯誤,由於devServer缺乏一個配置disableHostCheck: true,這樣的一個配置,不少文檔上面都沒有說明,字面上面的意思不要去檢查Host,這樣設置,即可以繞過這一層檢驗,設置的配置項在optionsSchema.json中,issue能夠看這裏

相關文章
相關標籤/搜索