这篇文章将介绍如何利用 webpack 进行单页面应用的开发,算是我在实际开发中的一些心得和体会,在这里给大家做一个分享。webpack 的介绍这里就不多说了,可以直接去官网查看。 关于这个单页面应用大家可以直接去我的github上查看https://github.com/huangshuwei/webpackForSPA,我将结合这个项目去介绍。如果大家觉得这篇文章有不妥的地方,还请指出。

这篇文章的目的是解决我们在开发中会遇到的问题,不是一篇基础教程,还请谅解。

项目目录

我将根据这个目录结构进行讲解

  • dist:发布的文件目录,即webpack编译输出的目录

  • libs:放置公共的文件,如js、css、img、font等

  • mockServer:模拟后端服务,即用webpack开发时模拟调用的后端服务(用nodejs服务模拟)

  • node_modules:项目依赖的包

  • src:资源文件,里面包含css、font、html、img、js

  • package.json:项目配置

  • webpack.config.js:webpack的配置文件

项目的使用

建议先运行一下这个项目,有一个大致的了解,再往下阅读。使用说明:

首先克隆一份到你的本地
$ git clone https://github.com/huangshuwei/webpackForSPA.git然后 cd 到 ‘webpackForSPA’目录下
$ cd webpackForSPA接着你可以运行不同的命令查看结果发布模式:
$ npm run build开发模式:
$ npm run dev热更新模式
$ npm run dev-hrm如果使用了热更新模式,并且想要结合后端服务形式运行,那么cd 到‘mockServer’目录下,并执行node 服务:
$ cd mockServer$ node server.js

区分开发、热更新、发布模式

一般开发时和发布时是不同的,比如开发时文件的访问目录包含‘dist’目录,但是发布上线时,一般会把‘dist’文件夹去掉。
当然还有其他的一些细节不同。

开发模式:

  • 能看到webpack编译输出的文件

  • js、css、html文件不需要压缩

  • 可以正确的运行编译输出后的文件

  • 这种模式一般只是用来看webpack编译输出后的文件是否正确

热更新模式:

  • 看不到webpack编译输出的文件

  • js、css、html文件不需要压缩

  • 更改完文件后无需重新编译并自动刷新浏览器

  • 可以结合后端服务开发,避过浏览器同源策略,如结合java、.net服务等

发布模式:

  • 能看到webpack编译输出的文件

  • js、css、html文件压缩

  • 文件的层级目录不需要包含‘dist’目录

我区分开发、热更新、发布模式是通过配置‘package.json’文件的运行命令,有些人是通过创建多个不同的webpack的配置文件来达到想要的效果。

像这个项目就是使用了多个webpack的配置文件。

配置命令

这是在 package.json 文件中配置的

// package.json 文件
...
"scripts": {"build": "webpack  --profile --progress --colors --display-error-details","dev": "webpack  --display-modules --profile --progress --colors --display-error-details","dev-hrm": "webpack-dev-server --config"},
...
  • color 输出结果带彩色,比如:会用红色显示耗时较长的步骤

  • profile 输出性能数据,可以看到每一步的耗时

  • progress 输出当前编译的进度,以百分比的形式呈现

  • display-modules 默认情况下 node_modules 下的模块会被隐藏,加上这个参数可以显示这些被隐藏的模块

  • display-error-details 输出详细的错误信息

  • webpack-dev-server 将会开启热更新

  • 更多请参考官网 cli

配置好了package.json文件,我们就可以这样运行

// 开发模式
npm run dev// 热更新模式
npm run dev-hrm// 发布模式
npm run build

配置变量标识

配置完了命令,当我们运行不同的命令时,我们可以通过‘process.env.npm_lifecycle_event’去获取当前运行的命令,根据不同的命令,我们可以按照自己的需要做相应的处理。比如开发模式时,允许开启调试,静态资源不要压缩;发布模式时,不允许调试,静态资源要压缩。具体如下:

// webpack.config.js// 获取当前运行的模式
var currentTarget = process.env.npm_lifecycle_event;var debug,          // 是否是调试devServer,      // 是否是热更新模式minimize;       // 是否需要压缩if (currentTarget == "build") { // 发布模式debug = false, devServer = false, minimize = true;} else if (currentTarget == "dev") { // 开发模式debug = true, devServer = false, minimize = false;} else if (currentTarget == "dev-hrm") { // 热更新模式debug = true, devServer = true, minimize = false;
}

基础配置

配置路径

为了方便我们频繁使用路径,如下配置

// webpack.config.js
var PATHS = {// 发布目录publicPath: debug ? '/webpackForSPA/dist/' : '/webpackForSPA/',// 公共资源目录libsPath: path.resolve(process.cwd(), './libs'),// src 资源目录srcPath: path.resolve(process.cwd(), 'src'),
}

配置别名

webpack的别名的目的就是简化我们的操作,引用资源时直接使用别名即可(和 seajs 里的别名用法一样)。配置如下:

// webpack.config.js
...
resolve:{alias: {// jsjquery: path.join(PATHS.libsPath, "js/jquery/jquery"),underscore: path.join(PATHS.libsPath, "js/underscore/underscore.js"),// cssbootstrapcss: path.join(PATHS.libsPath, "css/bootstrap/bootstrap-3.3.5.css"),indexcss: path.join(PATHS.srcPath, "css/index.css"),}
}
...

配置webpack编译入口

// webpack.config.js
...
entry:{// 入口 jsindex: './src/js/index.js',// 公共js包含的文件common: [path.join(PATHS.libsPath, "js/jquery/jquery.js"),path.join(PATHS.libsPath, "js/underscore/underscore.js")],
}
...

配置webpack编译输出

// webpack.config.js
...
output:{// 输出目录path: path.join(__dirname, 'dist'),// 发布后,资源的引用目录publicPath: PATHS.publicPath,// 文件名称filename: 'js/[name].js',// 按需加载模块时输出的文件名称chunkFilename: 'js/[name].js'
}
...

提取css到单独的文件

当我们在js文件中通过require('')引用js时,webpack 默认会将css文件与当前js文件打包一起,但是这种方式会阻塞页面的加载,因为css的执行要等待js文件加载进来。所以我们会把css从js文件中提取出来,放到一个单独的css文件中。这时我们要使用webpack的插件:extract-text-webpack-plugin,配置如下:

引入插件

// webpack.config.js
var ExtractTextPlugin = require("extract-text-webpack-plugin");

配置 loader

// webpack.config.js
...
loaders: [{test: /\.css$/,loader: ExtractTextPlugin.extract("style-loader", "css-loader!postcss-loader")},...
]
...

配置 plugins

// webpack.config.js
...
plugins:[new ExtractTextPlugin("css/[name].css", {allChunks: true}),...
]
...

公共js打包

项目中,我们通常会有公共的js,比如 jquery、bootstrap、underscore 等,那么这时候我们需要将这些公共的js单独打包。这时我们需要用webpack自带的插件:

// webpack.config.js
...
plugins:[// 会把 ‘entry’ 定义的 common 对应的两个js 打包为 ‘common.js’new webpack.optimize.CommonsChunkPlugin("common", 'js/[name].js', Infinity),
]
...

资源添加版本号

项目上线后,资源的版本号十分重要。资源没有版本号,即使重新发布,客户端浏览器可能会把老的资源缓存下来,导致无法下载最新的资源。webpack 支持给资源添加版本号,不仅仅是js、css,甚至font、img都可以添加版本号。我们可以通过webpack中的‘chunkhash’来解决。

首先要了解下webpack 中 [hash]、[chunkhash]、[chunkhash:8]的区别。

  • [hash]:webpack编译会产生一个hash值

  • [chunkhash]:每个模块的hash值

  • [chunkhash:8]:取[chunkhash]的前8位

推荐发布模式使用版本号,其他模式无需使用,热更新模式不支持‘chunkhash’,但是支持‘hash’

资源加版本号,那么我们的输出的部分都要做改动,并且要区分当前的命令模式,如下:

// webpack.config.js
...
output:{// 输出目录path: path.join(__dirname, 'dist'),// 发布后,资源的引用目录publicPath: PATHS.publicPath,// 文件名称filename: devServer ? 'js/[name].js' : 'js/[name]-[chunkhash:8].js',// 按需加载模块时输出的文件名称chunkFilename: devServer ? 'js/[name].js' : 'js/[name]-[chunkhash:8].js'
}
...

输出公共js的地方也要改动:

// webpack.config.js
...
plugins:[// 会把 ‘entry’ 定义的 common 对应的两个js 打包为 ‘common.js’new webpack.optimize.CommonsChunkPlugin("common", "" + (devServer ? 'js/[name].js' : "js/[name]-[chunkhash:8].js"), Infinity),
]
...

页面自动引入含有版本号的文件

有个版本号后,我们考虑如何通过html引用这些含有版本号的js、css、font、img。webpack每次编译后的资源 chunkhash 会随着内容的变化而变化,所以我们不可能每次都手动的更改html这些资源的引用路径。这时我们要用到webpack的插件:html-webpack-plugin。这个插件的目的是生成html,也可以根据模板生成html,当然还有其他的功能,具体看插件介绍。下面是的配置:

引入插件

// webpack.config.js
var HtmlWebpackPlugin = require('html-webpack-plugin');

配置 plugins,生成需要的html

// webpack.config.js
...
plugins:[new HtmlWebpackPlugin({filename: 'index.html',template: __dirname + '/src/index.html',inject: 'true'}),new HtmlWebpackPlugin({filename: 'html/hrm.html',template: __dirname + '/src/html/hrm.html',inject: false,}),new HtmlWebpackPlugin({filename: 'html/home.html',template: __dirname + '/src/html/home.html',inject: false,}),
]
...

我们前面说过,webpack 默认只识别 js 文件,所以对于html也要使用对应的loader:

// webpack.config.js
...
loaders:[{test: /\.html$/,loader: "html"},
]
...

引用图片和字体

引用图片和字体,需要对应的loader,并且可以设置这些资源大小的临界值,当小于临界值的时候,字体或者图片文件会以base64的形式在html引用,否则则是以资源路径的形式引用。如下:

// webpack.config.js// 图片 loader
{test: /\.(png|gif|jpe?g)$/,loader: 'url-loader',query: {/**  limit=10000 : 10kb*  图片大小小于10kb 采用内联的形式,否则输出图片* */limit: 10000,name: '/img/[name]-[hash:8].[ext]'}
},// 字体loader
{test: /\.(eot|woff|woff2|ttf|svg)$/,loader: 'url-loader',query: {limit: 5000,name: '/font/[name]-[hash:8].[ext]'}
},

资源文件的压缩

js、css、html的压缩是少不了的,webpack 自带了压缩插件,如果某些对象名称不想被压缩,可以排除不想要压缩的对象名称。配置如下:

// webpack.config.js
...
plugins:[new webpack.optimize.UglifyJsPlugin({ mangle: { // 排除不想要压缩的对象名称except: ['$super', '$', 'exports', 'require', 'module', '_']},compress: {warnings: false},output: {comments: false,}})
]
...

使用jquery、underscore

通过webpack编译输出后的项目中,虽然页面已经引用了jquery、underscore,但是还是无法直接使用‘$’、‘_’对象,我们可以这样:

var $ = require('jquery');
var _ =  require('underscore');

但是这样实在不方便,如果我们就是要使用‘$’、‘_’对象直接操作,webpack 内置的插件可以帮我们解决。具体如下:

// webpack.config.js
new webpack.ProvidePlugin({$: "jquery",jQuery: "jquery","window.jQuery": "jquery","_": "underscore",}),

代码分割,按需加载

在单页面应用中,当我们加载其他的模板文件时,想要引用这个模板文件对应的js。如果我们通过这种方式require(),那么webpack会将这个模板文件对应的js也会和当前js打包成一个js。如果项目比较大,那么js文件也将越来越大。我们希望的是加载模板文件的时候动态的引用这个模板文件对应的js。那么我们可以通过 require.ensure()的方式。

比如现在有两个导航菜单:

<ul>
<li><a href="#home">home</a></li>
<li><a href="#hrm">HRM</a></li>
</ul>

我们给这两个菜单绑定点击事件,当点击‘home’时引用对应的‘home.js’;当点击‘HRM’时引用对应的‘hrm.js’,那么大致可以这样:

function loadJs(jsPath) {var currentMod;if (jsPath === './home') {require.ensure([], function (require) {currentMod = require('./home');}, 'home');}else if (jsPath === './hrm') {require.ensure([], function (require) {currentMod = require('./hrm');}, 'hrm');}
}

全局环境变量

有时我们只有在开发过程中,才想输出log日志。可以用以下webpack内置的插件解决:

// webpack.config.js
...
plugins:[new webpack.DefinePlugin({// 全局debug标识__DEV__: debug,}),]
...

这时代码中就可以这么写了:

if (__DEV__) {console.log('debug 模式');
}

清空发布目录

发布前清空发布目录是有必要的,我们可以通过‘clean-webpack-plugin’插件解决:

引入插件:

// webpack.config.js
var CleanWebpackPlugin = require('clean-webpack-plugin');

配置plugins:

// webpack.config.js
...
plugins:[new CleanWebpackPlugin(['dist'], {root: '', // An absolute path for the root  of webpack.config.jsverbose: true,// Write logs to console.dry: false // Do not delete anything, good for testing.}),
]
...

热更新结合后端服务

热更新

热更新可以在你代码改变的时候即时编译输出,不用每次都要从都重新编译一遍,并且除了第一次编译比较慢,后面的编译都是增量编译,速度很快。有了这个功能,我们就不需要,每次都从头编译一次了。配置如下:

// webpack.config.js
...
plugins: [// Enable multi-pass compilation for enhanced performance// in larger projects. Good default.new webpack.HotModuleReplacementPlugin({multiStep: true}),
],
devServer: {// Enable history API fallback so HTML5 History API based// routing works. This is a good default that will come// in handy in more complicated setups.historyApiFallback: true,// Unlike the cli flag, this doesn't set// HotModuleReplacementPlugin!hot: true,inline: true,// Display only errors to reduce the amount of output.stats: 'errors-only',host: "localhost", // Defaults to `localhost`   process.env.HOSTport: "8080",  // Defaults to 8080   process.env.PORT
}
...

这时我们只要打开浏览器,输入:localhost:8080/ 就能看到结果,并且在你修改某些源文件后,浏览器会自动刷新,就能看到webpack 即时编译输出的结果,而不需要重新编译。

结合后端服务

我们在使用webpack开发时难免要结合后端服务开发,比如我们用webstorm 编译器开发项目,需要调用java的服务,由于有同源策略问题,这时我们会收到相关报错信息。这时我们可以通过代理的方式绕过同源策略。
这里我用nodejs 模拟一个后端服务,如下:

// ~/mockServer/server.jsvar http = require('http');var content = '▍if you see that,It means you have get the correct data by backend server(mock data by nodejs server)!';var srv = http.createServer(function (req, res) {res.writeHead(200, {'Content-Type': 'application/text'});res.end(content);
});srv.listen(8888, function() {console.log('listening on localhost:8888');
});

接下来我们需要这样配置去调用这个nodejs 的服务。
首先将热更新配置的代码修改为:

// webpack.config.js
...
plugins: [// Enable multi-pass compilation for enhanced performance// in larger projects. Good default.new webpack.HotModuleReplacementPlugin({multiStep: true}),
],
devServer: {// Enable history API fallback so HTML5 History API based// routing works. This is a good default that will come// in handy in more complicated setups.historyApiFallback: true,// Unlike the cli flag, this doesn't set// HotModuleReplacementPlugin!hot: true,inline: true,// Display only errors to reduce the amount of output.stats: 'errors-only',host: "localhost", // Defaults to `localhost`   process.env.HOSTport: "8080",  // Defaults to 8080   process.env.PORTproxy: {'/devApi/*': {target: 'http://localhost:8888/',secure: true,/** rewrite 的方式扩展性更强,不限制服务的名称* */rewrite: function (req) {req.url = req.url.replace(/^\/devApi/, '');}}}
}
...

然后配置一个全局的环境变量,通过DefinePlugin

// webpack.config.js
...
plugins: [new webpack.DefinePlugin({__DEVAPI__: devServer ? "/devApi/" : "''",}),
]
...

最后在调用服务的地方,只需要在调用地址前添加 __DEVAPI__全局环境变量即可,如:

$.ajax({url: __DEVAPI__ + 'http://localhost:8888/',data: {},type: 'get',dataType: 'text',success: function (text) {}})

这样在热更新的模式下,当有__DEVAPI__ 的地方就会自动识别为/devApi/,而这里会通过代理处理帮你重写掉,绕过同源策略。

自动打开浏览器

虽然以上的工作几乎已经满足我们对webpack的要求了,但是我们还想懒一点,想在热更新模式下,编译完成后自动打开浏览器。那么我们可以通过这个插件open-browser-webpack-plugin解决:

引用插件

// webpack.config.js
var OpenBrowserPlugin = require('open-browser-webpack-plugin');

配置插件,这个配置要根据项目的具体情况去配置:

// webpack.config.js
...
plugins: [new OpenBrowserPlugin({url: 'http://localhost:8080' + PATHS.publicPath + 'index.html'})
]
...

总结

以上就是这篇文章的主要内容,希望通过这篇文章能够给大家带来一些启发。如果有觉得哪里不对,或者不合理的地方,欢迎指出。其实webpack还有一个关于版本号的bug,不知道是不是有人解决了,如果有人已经解决了,还请分享。

webpack 单页面应用实战相关推荐

  1. 前端七十二变之vue单页面项目实战

    1.组件嵌套 将单文件组件组合在一起有两种方式,一种是嵌套方式,一种用路由的方式.嵌套的方式代码如下: 下图示中,假设组件A中要嵌入组件B <template>// 在A组件中使用B组件& ...

  2. Bootstrap实战 - 单页面网站

    一.介绍 单页面结构简单.布局清晰,常常用来做手机 App 或者某个产品的下载介绍页面.现在,展示型网页整体趋向于单页网站设计,这样一次性把核心信息展现出来,对于用户来说更加直观和简单,能够快速了解一 ...

  3. 【Web API系列教程】1.3 — 实战:用ASP.NET Web API和Angular.js创建单页面应用程序(上)

    前言 在传统的web应用程序中,客户端(浏览器)通过请求页面来启动与服务器的通信.然后服务器处理该请求,并发送HTML页面到客户端.在随后页面上的操作中--例如,用户导航到一个链接或提交一个包含数据的 ...

  4. [项目实战] 使用Idea构建单页面Vue3项目(不使用node、npm)

    前言 某天张三对小花说,我需要在一台新电脑上实现一个前端的漂亮页面:比如京东手机首页(m.jd.com). 小花这时吭哧吭哧的去新电脑上安装nodejs.npm,然后在本地使用npm构建vue3项目, ...

  5. Vue2+VueRouter2+Webpack+Axios 构建项目实战2017重制版(一)基础知识概述

    Vue2+VueRouter2+Webpack+Axios 构建项目实战2017重制版(一)基础知识概述 前言 2016年,我写了一系列的 VUE 入门教程,当时写这一系列博文的时候,我也只是一个菜鸟 ...

  6. Vue2+VueRouter2+Webpack+Axios 构建项目实战2017重制版(二)安装 nodejs 环境以及 vue-cli 构建初始项目

    Vue2+VueRouter2+Webpack+Axios 构建项目实战2017重制版(二)安装 nodejs 环境以及 vue-cli 构建初始项目 在上一篇<Vue2+VueRouter2+ ...

  7. 基于 vue2 + vuex2 构建一个具有 40 多个页面的大型单页面应用 饿了么

    前言 初学vue时曾在网上搜索vue的实战项目源码,无奈大部分都是简单的demo,对于深究vue没有太大的帮助,剩下的一些大部分都是像音乐播放器,知乎论坛之类的展示类项目,交互没有预期那么复杂.但我们 ...

  8. Java生鲜电商平台-深入订单拆单架构与实战

    Java生鲜电商平台-深入订单拆单架构与实战 Java生鲜电商中在做拆单的需求,细思极恐,思考越深入,就会发现里面涉及的东西越来越多,要想做好订单拆单的功能,还是相当有难度, 因此总结了一下拆单功能细 ...

  9. Vue单页面在ios10系统上出现白屏的bug

    一个bug 你用Vue做了一个单页面应用,它在一切设备上都工作正常,但是突然有一天,你的测试和你说,这个网站在iOS 10上跑不起来,怎么办?于是你打开你电脑上的Chrome浏览器,工作正常:打开Sa ...

最新文章

  1. 学好python的技巧_初学Python搞不懂基础怎么学得好?掌握这9个技巧你也可以做大神...
  2. REUSE_ALV_GRID_DISPLAY事件子过程和cl_gui_grid类的事件对应关系
  3. C++编程经验总结1
  4. 教你在Ubuntu上体验Mac风格
  5. 容器编排技术 -- Kubernetes 为 Namespace 配置Pod配额
  6. C++中时间相关函数的使用
  7. 百度seo排名规则_百度seo排名优化要点讲解(已帮助5184人)
  8. 序列化和反序列化(六)——Java对象的网络传输(一)
  9. 《Head First设计模式》 读书笔记05 工厂模式(二)
  10. c语言 队列方法的编写
  11. VMware ESXI 5.5 注册码
  12. 腾讯火力全开“吃鸡”:下一个游戏行业风口怎能错过?
  13. vue中实现图片预览功能
  14. 关于计算机的小故事英语作文,关于小故事英语作文阅读
  15. react如何请求amr文件流接口-优化版
  16. php file get contents 总是超时,file_get_contents超时问题及解决方案
  17. 各应用市场(安卓)的入口整理:
  18. 【无标题】关于BC25连接电信物联网平台的问题(批量产品在广东连接不到物联网平台,在合肥测试是可以的)
  19. VSCode前端开发工具插件--LiveServer实时刷新网页
  20. vmware虚拟机桥接模式在有线/无线双网卡解决方案

热门文章

  1. PSVR开发者需要了解的9件事
  2. Java远程发送表单信息,java – 从html表单读取POST数据发送到serversocket
  3. Nature子刊:用机器学习揭露人类基因调控背后的“语法”
  4. 广州新房都智能成这样了???
  5. 今日可抢回程火车票,实测两款GitHub开源抢票插件,所有坑我们都帮你踩过了...
  6. GitHub一日千星:开头一张图,自动变成《我的世界》、乐高、十字绣风格,有Python就能跑...
  7. 全国“最高”的视觉竞赛,华为Atlas打通遥感图像智能分析任督二脉
  8. ZJOI2019 Day2 游记
  9. linux下文件的压缩和解压缩
  10. debian 8 网桥