webpack 配置 Vue 多页应用 —— 从入门到放弃

一直以来,前端享有无需配置,一个浏览器足矣的优势,直到一大堆构建工具的出现,其中 webpack 就是其中最复杂的一个,因此出现了一个新兴职业 『webpack 配置工程师』。其实 webpack 配置本质上来说也就是编程,难点在于各种 loader 和 plugin 的选择和合理搭配,下面就由我来捋一捋。

使用 webpack 配置单页应用的教程很多,直接使用官方的 vue-cli 工具就非常方便,今天我要说的是如何配置一个多页应用,这如果会了,单页应用也当然不在话下。

先放下项目地址项目地址 vue-multi-page-webpack

逼不多装,先说下要达成的效果。假设我们有两个目录


  1. |-- page1
  2. |-- page2
  3. |-- page3

要做到

  • 每个页面都有自己的配置项,包括但不仅限于 title、脚本等
  • 可以打包所有目录及其子目录
  • 可以做到只开发或打包指定目录(例如目录很多,我们只想要开发其中一个页面)
  • 余下的就是一些基本的配置项了,这些单页面配置也有,如 eslint、babel、postcss 等等

让我们愉快的开始吧!注意:一些很基础的配置我就不提了,可以在我的 github 上看完整配置项

按照惯例,配置分为开发和生产的配置,可以分为 webpack.base.config.js webpack.dev.config.js webpack.prod.config.js

先从webpack.base.config.js说起


  1. entry: {
  2. vendor: ['vue'],
  3. },
  4. output: {
  5. path: consts.DIST_PATH,
  6. filename: '[name].[chunkhash:7].js',
  7. publicPath: '/'
  8. },

因为每个页面都要用到 vue,因此把它放到 vendor 里,这里入口还不急配置入口文件,因为上面我们说过要实现按需开发和打包,因此这里是动态配置的,后面会说到。output 里都是常规配置,可以把一些常量单独提取出来。

接下来配置 loader,用 eslint-loader 举例说明


  1. {
  2. enforce: 'pre',
  3. test: /\.(js|vue)$/,
  4. exclude: /node_modules/,
  5. loader: 'eslint-loader',
  6. options: {
  7. formatter: require('eslint-friendly-formatter'),
  8. emitWarning: true,
  9. }
  10. }

loader 配置大同小异,一般就是其中的 testloader 值不同,enforce: 'pre'表示在其它 loader 之前执行,test 表示对哪种类型的文件转换,loader 表示使用的 loader,对于每种 loader 来说最好去相应的官方文档上去看看用法。这里说下我用到的。

eslint-loader 进行代码检查的,在根目录下配置一下 eslintrc ,因为是 vue 项目,所以 plugin 增加 vue,这需要引入 eslint-plugin-vue,然后 env 设置 es6: true 开启 ES6,需要注意的是最新版本可能有坑,如果你用的以前的 eslint 配置,可能会报错,可以看看 这个,不要问我为什么知道... 其它配置可参考我的。

babel-loader json-loader vue-html-loader 处理对应类型的文件,没什么好说的。

值得注意的是对于图片的处理,这里使用了 file-loader,要注意 outputPath 的配置,很容易导致图片404。

对于 css 和 vue 文件,配置有些不同,稍后再说。

接下来就是重点 webpack.dev.config.js ,之前说过要按需开发,首先科普下一个小知识。我们创建 test.js


  1. // test.js
  2. console.log(process.env.a)
  3. a=b node test.js // 输出b

之前的 a=b 会被写入环境变量中,通过 process.env 可以得到,通过这样就可以实现按需开发。

以前都是直接运行 webpack 命令带上 开发环境的配置文件使用自带的 server 来开发和热更新,这对于带参数按需开发不现实,这里使用 express 和 webpack-dev-middleware 加 webpack-hot-middleware 来达到目的,其实配置差不多。创建一个 server.js


  1. server.js
  2. const express = require('express')
  3. const webpack = require('webpack')
  4. const webpackDevMiddleware = require('webpack-dev-middleware')
  5. const webpackHotMiddleware = require('webpack-hot-middleware')
  6. const app = express();
  7. const config = require('./webpack.dev.config.js')
  8. const compiler = webpack(config)
  9. const devMiddleware = webpackDevMiddleware(compiler, {
  10. stats: {
  11. colors: true,
  12. chunks: false,
  13. children: false,
  14. },
  15. lazy: false,
  16. publicPath: '/',
  17. })
  18. app.use(devMiddleware)
  19. app.use(webpackHotMiddleware(compiler))
  20. app.listen(3000, function () {
  21. console.log('Modules listening on port 3000!\n')
  22. })

我们使用 module=page1 node server.js 启动,这时 page1 就是环境变量 module 的值。

webpack.dev.config.js 配置中,


  1. const moduleName = process.env.module
  2. let moduleList = []
  3. if (moduleName) {
  4. moduleList = moduleName.split(',')
  5. } else {
  6. moduleList = utils.allModules
  7. }

这样就可以得到需要开发的模块,可以看到其实也支持 module=page1,page2 这样的形式。

接着就需要将moduleList 中的每一项提取为入口文件


  1. const configList = utils.loadModules(moduleList)
  2. const moduleContent = utils.getModuleConfigs(configList)

这里utils 的代码有点长,就不放在这了,可以在 GitHub 上看,主要说说是怎么做的。上面moduleList = ['page1', 'page2'],寻找 src 目录下的每一项,即我们要开发的目录,对于每个模块,可能有自己的 title 和 需要加载的脚本,因此在每个目录里需要一个配置文件,这里看下page1 的配置文件 config.yml


  1. entry: 'page1'
  2. template:
  3. title: 'page1'
  4. scripts:
  5. - 'https://unpkg.com/vue-router/dist/vue-router.min.js'
  6. - 'https://unpkg.com/vuex/dist/vuex.min.js'

这是一个 .yml 文件,当然你用 .json 文件什么的其实也可以,这里规定每个模块都需要有一个 config.yml。loadModules 方法把 config.yml 的配置读取出来,生成


  1. [{
  2. entry: 'page1',
  3. template: {
  4. title: 'page1',
  5. scripts: ['script1', 'script2']
  6. }
  7. }]

根据这个配置文件,再来生成之前提到的入口配置,{ 'page1/page1': [ './src/page1' ] } 即最后生成的是 page1 目录下的 page1.js,与此同时,为每一个目录生成一个 html 文件,这里使用了 HtmlWebpackPlugin 插件,上面的配置文件中有 title 和 scripts,可以传入 html 模板中生成相应的部分。

```<!DOCTYPE html>
<html>
<head>
<title><%= htmlWebpackPlugin.options.title %></title>
<meta charset="utf-8">
</head>
<body>
<h1>HELLO WORLD!</h1>
<div id="app"></div>
<!-- 如果有内嵌的js可以放在这里 -->
<!-- <script defer></script> -->

  1. &lt;!-- 通过参数插入的 script --&gt;
  2. &lt;% for (var i = 0; i &lt; htmlWebpackPlugin.options.scripts.length; i++) { %&gt;
  3. &lt;script src=&lt;%= htmlWebpackPlugin.options.scripts[i] %&gt;&gt;&lt;/script&gt;
  4. &lt;% } %&gt;

</body>

</html>


  1. <p>使用 <code>HtmlWebpackPlugin</code> 插件时需要注意 template 即上面的模板,chunks 即打包成的 js 文件,vendor 是公用的,里面有 vue,放在最前面,后面就是每个页面自己的 js 文件,<code>inject: 'body'</code> 表示放在 body 前,当然你也可以添加自己想要的参数,按照上面模板的方式插进去。</p>

const getHtmlTemplatePlugin = config => {

return new HtmlWebpackPlugin({

filename: ${config.entry}/index.html,

template: path.join(consts.ROOT_PATH, 'build/index.tpl'),

title: config.template.title,

minify: {

removeComments: true,

collapseWhitespace: true,

minifyCSS: true,

minifyJS: true,

collapseBooleanAttributes: true,

},

inject: 'body',

chunks: ['vendor', config.chunkName],

scripts: config.template.scripts || [],

})

}


  1. <p>接着就是开发环境需要的 loader ,首先是 css 文件,这里使用了 postcss,为了方便在根目录添加 <code>.postcssrc</code> 文件,postcss-import 可以方便的用 import 命令引用 css 文件,postcss-cssnext 就是使用一些新的语法,postcss-nested 可以使用嵌套的语法,如果你是开发移动端,使用了 flexible.js,使用 postcss-px2rem 可以将 px 转成 rem,注意你必须在上面的模板文件中添加 flexible.js。</p>

module.exports = {

"plugins": {

"postcss-import": {},

"postcss-cssnext": {},

"postcss-nested": {},

"postcss-px2rem": { remUnit: 75 },

}

}


  1. <p>这里把 css的配置封装一下,在生产环境中使用 <code>ExtractTextPlugin</code> 将css单独提取成文件,开发环境则不需要,<strong>注意三个loader</strong>的顺序</p>

getCssLoaderConfig: function(isProduction) {

const config = [{

loader: 'css-loader',

options: {

modules: true,

importLoaders: 1

}

}, {

loader: 'postcss-loader',

}]

return isProduction ? ExtractTextPlugin.extract({

fallback: 'style-loader',

use: config,

}) : [{ loader: 'style-loader '}].concat(config)

},


  1. <p>对于 vue 文件来说类似,这里就不放代码了。</p>
  2. <p>接下来就是插件, CommonsChunkPlugin 就是把之前 入口的 vendor 单独打包,HotModuleReplacementPlugin 就是实现开发环境时热更新。</p>

new webpack.optimize.CommonsChunkPlugin({

name: ['vendor'],

filename: '[name].[hash:7].js',

minChunks: 88

}),

new webpack.HotModuleReplacementPlugin(),


  1. <p>当然要实现热更新还是光有上面的插件还是不够的,需要给每个入口增加一个<code>webpack-hot-middleware/client?reload=true</code>,这里通过代码来增加</p>

Object.keys(moduleContent.entry).forEach(key => {

if (Array.isArray(moduleContent.entry[key])) {

moduleContent.entry[key].push('webpack-hot-middleware/client?reload=true')

}

})


  1. <p>到此,你就可以开心的使用 <code>module=page1 node server.js</code> 来开发了 page1 模块了,是不是很简单。加入要开发所有模块,不带 <code>module=page1</code> 即可,因为有个可以读取 src 目录下的所有目录</p>

get allModules() {

return fs.readdirSync(consts.SRC_PATH)

}


  1. <p>还有一点要提一下的就是假如是嵌套的目录配置文件如下其实代码也是适用的,只要给每个子目录添加相应的配置文件即可</p>

  1. <p>最后再来说一说生产环境的,其实和开发环境大体类似。需要一个 build.js,这里比开发环境简单一点,只需运行配置就好。</p>

const ora = require('ora')

const webpack = require('webpack')

function runWebpack() {

const webpackConf = require('./webpack.prod.config')

const spinner = ora('building for production...').start()

webpack(webpackConf, (err, stats) => {

spinner.stop()

if (err) throw err

console.log(stats.toString({

colors: true,

modules: false,

children: false,

chunks: false,

chunkModules: false,

}))

})

}

runWebpack()


  1. <p>生产环境配置有一点不同的是要指定output目录,一般可以是 CDN 或 服务器的目录,这里使用了 dist 目录,通过 CleanWebpackPlugin 插件可以在每次打包之前清除 dist 目录,UglifyJsPlugin 压缩 js 文件,ExtractTextPlugin 插件就是提取 css 为单独文件,即上面在使用<code>ExtractTextPlugin.extract</code> 时处理的css。最后为了调试方便,可以使用一段代码生成最后的配置文件。</p>

fs.writeFile('webpack.prod.config.json', JSON.stringify(prodConfig, null, 2), (err) => {

if (err) throw err

console.log('Dev config file generated')

})


  1. <p>至此,配置基本结束,还有一点,用 <code>module=page1 node server.js</code> 这样的命令体现不出逼格,因此写个 makefile 文件,使命令简单点。</p>

.PHONY: build

.PHONY: dev

dev:

@sudo module=${module} node build/dev.js

build:

@sudo module=${module} node build/build.js


  1. <p>当使用 <code>make dev</code> 即运行对应的命令,开发所有模块,<code>make dev module=page1</code> 即单独开发 page1,同理 <code>make build</code> 打包命令。其实make 命令是很强大的,可以更方便的执行多条命令,让自动化变得更简单,我也只会这一点毛皮。</p>
  2. <p>在最后的一天工作日终于写完了,拖延症晚期患者。如果本文及这个小项目对你有用,点个赞呗。项目地址<a href="https://github.com/donghaohao/vue-multi-page-webpack" rel="nofollow noreferrer">vue-multi-page-webpack</a></p>
  3. <p>溜了,溜了,赶火车肥家。大家春节愉快。</p>
  4. 原文地址:https://segmentfault.com/a/1190000013285878

webpack 配置 Vue 多页应用 —— 从入门到放弃的更多相关文章

  1. 11. webpack配置Vue

    一. 在webpack中配置vue 了解了webpack的原理和用法以后, 我们来引入Vue webpack原理和用法详解链接: cnblogs.com/ITPower/p/14467745.html ...

  2. 使用webpack配置vue项目代理 (超简单)

    我们都知道,前端开发跨域是一个很常见的问题,当然跨域的方法也有很多,现在我就给大家分享一个在vue项目中如何使用webpack做代理,步骤简单,操作方便,本人亲测,巨好使

  3. webpack配置vue项目

    npm init 安装webpack,webpack-cli 新建src目录,在src目录下新建main.js

  4. 自学vue第二天,从入门到放弃(生命周期的理解)

    生命周期的理解 beforeCreate 创建前 数据还没有监听,没有绑定到vue对象实例,同时也没有挂载对象 没有数据也没有方法 created 在创建后 数据和方法已经 data数据已经绑定好了 ...

  5. VUE的学习_从入门到放弃(一)

    一.vue的功能及作用 工作方式如下 1.不用操作DOM 2.单页面应用web项目 简称:SPA 3.当下各种新框架都采用的类似Vue或者类似React的语法去作为主语法,微信小程序/MpVue... ...

  6. 每天记录一点:NetCore获得配置文件 appsettings.json vue-router页面传值及接收值 详解webpack + vue + node 打造单页面(入门篇) 30分钟手把手教你学webpack实战 vue.js+webpack模块管理及组件开发

    每天记录一点:NetCore获得配置文件 appsettings.json   用NetCore做项目如果用EF  ORM在网上有很多的配置连接字符串,读取以及使用方法 由于很多朋友用的其他ORM如S ...

  7. webpack构建vue项目(配置篇)

    最近公司要求用vue重构项目,还涉及到模块化开发,于是乎,我专门花了几天的时间研究了一下webpack这个目前来看比较热门的模块加载兼打包工具,发现上手并不是很容易,现将总结的一些有关配置的心得分享出 ...

  8. 前端笔记之Vue(一)初识SPA和Vue&webpack配置和vue安装&指令

    一.单页面应用(SPA) 1.1 C/S到B/S页面架构的转变 C/S:客户端/服务器(Client/Server)架构的软件. C/S 软件的特点: ① 从window桌面双击打开 ② 更新的时候会 ...

  9. webpack+sass+vue 入门教程(三)

    十一.安装sass文件转换为css需要的相关依赖包 npm install --save-dev sass-loader style-loader css-loader loader的作用是辅助web ...

随机推荐

  1. CSS3 网格布局(grid layout)基础知识 - 隐式网格自己主动布局(grid-auto-rows/grid-auto-columns/grid-auto-flow)

    网格模板(grid-template)属性及其普通写法(longhands)定义了一个固定数量的轨道.构成显式网格. 当网格项目定位在这些界限之外.网格容器通过添加隐式网格线生成隐式网格轨道. 这些隐 ...

  2. DCloud-MUI:utils

    ylbtech-DCloud-MUI:utils 1.返回顶部 1.init mui框架将很多功能配置都集中在mui.init方法中,要使用某项功能,只需要在mui.init方法中完成对应参数配置即可 ...

  3. TCP打开文件传输(服务器端并发code)

    #include <stdio.h>#include <stdlib.h>#include <arpa/inet.h>#include <sys/types. ...

  4. 前端面试:问到GET和POST两种区别

    最直观的区别就是GET把参数包含在URL中,POST通过request body传递参数. "标准答案"(本标准答案参考自w3schools): GET在浏览器回退时是无害的,而P ...

  5. 优先队列 + 并查集 + 字典树 + 欧拉回路 + 树状数组 + 线段树 + 线段树点更新 + KMP +AC自动机 + 扫描线

    这里给出基本思想和实现代码 . 优先队列 : 曾经做过的一道例题       坦克大战 struct node { int x,y,step; friend bool operator <(no ...

  6. python 6:list.append(新元素)与list.insert(索引,新元素)(在列表末尾追加新元素、在索引处添加新元素)

    bicycles = ['trek', 'cannondale', 'redline', 'specialized'] print(bicycles) bicycles.append("ho ...

  7. python 字符编码的两种方式写法:# coding=utf-8和# -*- coding:utf-8 -*-

    python运行文件是总会出现乱码问题,为了解决这个问题,在文件开头加上: # coding=utf-8 或者 # -*- coding:utf-8  -*- # coding=<encodin ...

  8. springboot 的一般配置

    import javax.servlet.Filter; import org.springframework.boot.SpringApplication; import org.springfra ...

  9. SQLServer2008 关于CASE WHEN

    CASE WHEN的两种格式 1.简单Case函数 CASE sex WHEN '1' THEN '男' WHEN '2' THEN '女' ELSE '其他' END 2.Case搜索函数 CASE ...

  10. matplotlib之pyplot 知识点滴

    以下是一些常用地址链接,请参考 matplotlib 官方网址 plt.plot()函数细节 Matplotlib 中文用户指南 4.6 编写数学表达式 Python seaborn matplotl ...