前言
本实战为学习实践的第三个实战,延续之前的实战二NodeJS实战 实现前后端的联调
本次主题:使用 Webpack 进行前后端工程化开发
具体要求
回顾上一次实战 NodeJS实战—使用NodeJS实现前后端的联调
实战开始
1. 使用 X-TAG 封装点赞插件
- 先将上次写好的项目拿过来,这里我使用的 git 命令,详情可看 Git 入门级的操作 和 前端工程化预备知识(下)中关于 GIT 命令的部分
- 在桌面新建一个 GitPost 文件夹,进入文件夹,在文件夹中鼠标右键选择 Git Bash Here 选项(前提是需要参照上面的“入门级操作中”安装 git.exe 客户端),选中之后便会打开一个 git 命令行窗口
- 在命令行中拿 github 上的代码,github 项目 ``` //将 github 中的项目文件克隆至当前目录中 git clone https://github.com/sunxiaochuan/koatest.git //成功之后 退出命令行 exit
- 这个时候将 koatest 项目拖拽至 sublime 编辑器中
> <br />

2. 进入项目中并启动服务
- 打开 Cmder (windows 下命令行利器,建议使用这个)命令行终端
- 进入到 koatest 项目文件夹中
cd Desktop/GitPost/koatest //查看一下项目中的目录 ls
> 
- 启动服务,这里需要注意的是:**如果 node 版本过低的话启动是会报错的,建议上官网或通过其他途径升级到最新的稳定版**
//因为 node_modules 项目中存放依赖文件的文件夹的过于庞大,上传到 github 时已将其删除,所以这里要讲项目中所有的依赖包再重新装一遍 npm install //装完之后 启动服务 node app_o.js
- 这里我就是因为之前的版本是 6 + 的原因一直报错,后来将版本升级之后(我是到官网下载的稳定版然后安装的)就可以了
> <br />

- 这个时候在浏览器中查看页面是否打开成功,下图可以看到我这个已经打开成功了
> 
3. 接下来开始封装插件
- 打开项目中的 views -> index.html 文件,实际上封装的内容就是下面那一部分的 html
> <br />

- 使用的是 标签来替代
> 
- 上面的标签要如何实现呢?使用的是 [X-TAG](http://x-tag.github.io/docs#getting_started)<br />
下面是官方文档中的一个示例
//在这里注册的是 x-foo 的标签
xtag.register(‘x-foo’, {
//标签内容
content: ‘‹input/›’,
//生命周期
lifecycle:{
created: function(){},
inserted: function(){},
removed: function(){},
attributeChanged: function(){}
},
//方法
methods: {
someMethod: function(){}
},
//访问器
accessors: {
someAccessor: {
// links to the ‘some-accessor’ attribute
attribute: {},
set: function(){},
get: function(){}
}
},
//事件
events: {
tap: function(){},
focus: function(){}
}
});
- 我这里使用 [cdn](http://www.bootcdn.cn/x-tag/) 的方式将 X-TAG 的 js 文件引入 views -> index.html 页面中
- 在 public -> scripts 目录中新建一个 tags.js 文件<br />
编辑 tags.js 文件
//在这里注册的是 x-praise 的标签 xtag.register(‘x-praise’, { //标签内容 将 index.html 中注释的代码拿到这里做拼接 content: ‘
lifecycle: { created: function() {}, inserted: function() {}, removed: function() {}, attributeChanged: function() {} }, //方法 methods: { someMethod: function() {} }, //访问器 accessors: { someAccessor: { // links to the ‘some-accessor’ attribute attribute: {}, set: function() {}, get: function() {} } }, //事件 events: { tap: function() {}, focus: function() {} } });
- 之后在 index.html 文件中引入 tags.js
- 这个时候在命令行中启动服务,再刷新浏览器查看是否成功
node app_o.js
这个时候会发现 x-praise 标签里面的内容就是下面注释的 html 这个就证明**第一个步骤**用 x-praise 标签封装已经完成了
> <br />
###2. 使用 Gulp 编译 KOA2 源代码,并能监控源代码变化自动编译<br />
参考链接:[Gulp 中文网--入门指南](http://www.gulpjs.com.cn/docs/getting-started/)
1. 安装 gulp
//全局安装 gulp npm install —global gulp //作为项目的开发依赖(devDependencies)安装 npm install —save-dev gulp
2. 在项目根目录下创建一个名为 gulpfile.js 的文件,编辑 gulpfile.js 文件的话需要参照 [gulp API 文档](http://www.gulpjs.com.cn/docs/api/),下面是主要会使用的命令
gulp.src(globs[, options]) gulp.dest(path[, options]) //执行任务列表 先把后面的 deps 事件全部执行完成之后才会执行 name gulp.task(name[, deps], fn) //用这个实现监控源代码的变化 gulp.watch(glob[, opts], tasks)
3. gulp 实现编译还需要装一个插件 [gulp-babel](https://www.npmjs.com/package/gulp-babel)
//npm install —save-dev gulp-babel babel-preset-env 这个后面的 babel-preset-env 是使用规则,在上一次实战中在项目中已经安装过了 所以只需要安装 gulp-babel npm install —save-dev gulp-babel
4. 通过 [gulp-babel](https://www.npmjs.com/package/gulp-babel) 中的 **Usage** 实例来编辑 gulpfile.js 文件
//引入 gulp const gulp = require(‘gulp’); //引入 gulp-babel 编译需要的插件 const babel = require(‘gulp-babel’); //default 是默认的任务 gulp.task(‘default’, () =>{ //src 第一个参数就是需要编译的文件 src 目录下的所有目录中的以 .es 为后缀的文件 gulp.src(‘src/*/.es’) .pipe(babel({ presets: [‘es2015’,’stage-0’] })) .pipe(gulp.dest(‘build’)) });
5. 这个时候在项目中新建一个 src 目录,将需要编译的文件全都放到这个目录下
> <br />

6. 因为上面设置的是 .es 为后缀的文件所以需要将要编译的 js 文件的后缀改为 .es,这里只需要修该 app.js 的其他的都在之前修改过了
> 
7. 接下来就可以进行编译了
gulp
- 命令行中显示如下证明执行成功了
> 
8. 这个时候在编辑器中查看会发现多了一个 build 目录,里面的都是将 src 目录中 需要编译的 .es 文件编译好之后生成的文件
> 
9. 上面的就实现了编译的功能,下面就需要用 `gulp.watch()` 监控来监控源代码的变化 `gulp.task()` 实现自动的编译 ,先把刚才编译出来的 bulid 目录删掉
- 编辑 gulpfile.js 文件
//引入 gulp Gulp 中文网—入门指南 const gulp = require(‘gulp’); //引入 gulp-babel 编译需要的插件 gulp-babel const babel = require(‘gulp-babel’);
//gulp API 文档 //default 是默认的任务 现在的默认任务执行顺序是 praise -> fn -> default gulp.task(‘default’, [‘praise’], () => { //这里监控文件 监控到改变之后还是要执行 praise gulp.watch([‘src/*/.es’, ‘!src/public//.es’], [‘praise’]); });
//这里把编译任务名称改为 praise 这个事件会先于 default 执行
gulp.task(‘praise’, () => {
//src 第一个参数就是需要编译的文件 src 目录下的所有目录中的以 .es 为后缀的文件
//后面又加了一个 非的条件 除了 public 目录下的 .es 文件 因为这个是要用 webpack 来编译的,实现的是用 gulp 编译后端的代码 webpack 编译前端的 上面需要监视的文件与这个一样
gulp.src([‘src/*/.es’, ‘!src/public//.es’])
//这个是编译的时候要用到的规则
.pipe(babel({
presets: [‘es2015’, ‘stage-0’]
}))
//这个是编译之后文件放置的目录
.pipe(gulp.dest(‘./build’))
});
10. 这时在命令行中执行
gulp
- 查看命令行中的输出,先执行了 praise 执行完之后又执行了 default
> 
- 再在 sublime 中查看项目会发现自动创建了 build 目录 并将所有编译好的文件放到了里面
> 
- 现在再修改一下 src -> app.es 文件测试 是否监视到变化并自动编译了<br />
随便将一行空格删掉然后 ctrl + s 保存
> 
- 此时再去查看命令行的输出会发现自动执行了 praise 编译的任务,这个时候就说明**第二个步骤**成功了
> <br />
###3. 使用 Webpack 配置上线版本、开发版本配置文件,并能监控文件的变化自动刷新浏览器<br />
[webpack 官网](https://webpack.js.org/guides/getting-started/)
1. 在命令行中先 `ctrl + c` 退出刚才的监控模式,接着安装 webpack
npm install —save-dev webpack
2. 在项目中新建一个 webpack.config.js 配置文件,参照 [官网示例](https://webpack.js.org/guides/getting-started/#using-a-configuration)
- 编辑配置文件之前先修改几个 public -> scripts 目录下的 js 文件的名称<br />
修改 index.js 为 indexadd.js 是因为上面的 index.es 文件编译之后 名称会自动变为 index.js 会造成冲突<br />
修改 tags.js 为 tags.es 是因为我们的正则匹配的是 .es 为后缀的文件
> <br />

- 接着在 views -> index.html 中修改引入的 js 的名称
- 编辑 webpack.config.js 文件
//引入 path 模块 const path = require(‘path’); //引入 webpack 模块 const webpack = require(‘webpack’); module.exports = { //入口资源配置 需要编译的文件 entry: { index: [ path.join(dirname, ‘../src/public/scripts/index.es’), path.join(dirname, ‘../src/public/scripts/indexadd.js’) ], tags: [ path.join(dirname, ‘../src/public/scripts/tags.es’) ] }, //资源输出配置 资源输出的路径 output: { //资源输出时的名称 以 MD5 的形式 filename: ‘public/scripts/[name]-[hash:5].js’, //资源输出时的路径 path: path.join(dirname, ‘../build/‘) } };
3. 接下来是配置上线版本和本地版本
- 在项目中新建一个 config 目录,然后再分别建 上线和开发两个版本的 js 文件
> 
- 先将 webpack.config.js 中的代码全部分别复制到上面的两个配置文件中去,然后在 webpack.config.js 文件中引用上面新建的两个配置文件,分开是因为后面的压缩、打包等操作不对开发版本使用,只是操作线上版本的<br />
webpack.dev.js 文件
//这个是开发版本使用的 js 配置文件 //引入 path 模块 const path = require(‘path’); //引入 webpack 模块 const webpack = require(‘webpack’); module.exports = { //入口资源配置 需要编译的文件 entry: { index: [ path.join(dirname, ‘../src/public/scripts/index.es’), path.join(dirname, ‘../src/public/scripts/indexadd.js’) ], tags: [ path.join(dirname, ‘../src/public/scripts/tags.es’) ] }, //资源输出配置 资源输出的路径 output: { //资源输出时的名称 以 MD5 的形式 filename: ‘public/scripts/[name]-[hash:5].js’, //资源输出时的路径 path: path.join(dirname, ‘../build/‘) }, };
webpack.prod.js 文件
//这个是上线版本使用的 js 配置文件 //引入 path 模块 const path = require(‘path’); //引入 webpack 模块 const webpack = require(‘webpack’); module.exports = { //入口资源配置 需要编译的文件 entry: { index: [ path.join(dirname, ‘../src/public/scripts/index.es’), path.join(dirname, ‘../src/public/scripts/indexadd.js’) ], tags: [ path.join(dirname, ‘../src/public/scripts/tags.es’) ] }, //资源输出配置 资源输出的路径 output: { //资源输出时的名称 以 MD5 的形式 filename: ‘public/scripts/[name]-[hash:5].js’, //资源输出时的路径 path: path.join(dirname, ‘../build/‘) } };
webpack.config.js 文件
//引入 path 模块
const path = require(‘path’);
//引入 webpack 模块
const webpack = require(‘webpack’);
//引入 开发版本使用的 js 配置文件
const DevWebpack = require(‘./config/webpack.dev’);
//引入 上线版本使用的 js 配置文件
const ProdWebpack = require(‘./config/webpack.prod’);
//在这里做判断是用 开发版的还是上线版的
//这里做判断的话需要引用 better-npm-run 模块
switch(){
//如果是开发版本
case ‘dev’:
module.exports = DevWebpack;
break;
//如果是上线版本
case ‘prod’:
module.exports = ProdWebpack;
break;
//默认是开发版本
default:
module.exports = DevWebpack;
break;
}
- 安装 webpack.config.js 中引用的模块
//判断是用 开发版的还是上线版 所用到的模块 npm install —save-dev better-npm-run
- 安装 better-npm-run 之后就需要在 package.json 文件中进行配置,配置方法参照 [better-npm-run](https://www.npmjs.com/package/better-npm-run) 主要修改的是有注释的地方<br />
编辑 package.json 文件 需要在注意的是**下面中的注释只是参考,真正运行的时候需要全部删掉,不然会报错的哦**
{ “name”: “koatest”, “version”: “1.0.0”, “description”: “”, “main”: “index.js”, “scripts”: { “test”: “echo \”Error: no test specified\” && exit 1”, //前面的 webpackdev 是自定义的名称 : 后面的是用了什么东西是需要在下方添加的 “webpackdev”: “better-npm-run webpack:dev”, “webpackprod”: “better-npm-run webpack:prod” }, //设置执行的脚本 “betterScripts”: { //这个对应的是上面调用时使用的脚本名称 “webpack:dev”: { //这里面写执行的命令 “command”: “webpack —progress —colors”, //这个是他使用的版本 “env”: { “NODE_ENV”: “dev” } }, “webpack:prod”: { //这里面写执行的命令 “command”: “webpack —progress —colors”, //这个是他使用的版本 “env”: { “NODE_ENV”: “prod” } } }, “author”: “”, “license”: “ISC”, “dependencies”: { “koa”: “^2.0.0-alpha.8”, “koa-simple-router”: “^0.2.0”, “koa-static”: “^4.0.2”, “koa-swig”: “^2.2.1”, “protractor”: “^5.2.0”, “request”: “^2.83.0”, “request-promise”: “^4.2.2”, “selenium-standalone”: “^6.11.0” }, “devDependencies”: { “babel-polyfill”: “^6.26.0”, “babel-preset-es2015”: “^6.24.1”, “babel-preset-stage-0”: “^6.24.1”, “babel-register”: “^6.26.0”, “better-npm-run”: “^0.1.0”, “gulp”: “^3.9.1”, “gulp-babel”: “^7.0.0”, “jasmine-core”: “^2.8.0”, “karma”: “^1.7.1”, “karma-chrome-launcher”: “^2.2.0”, “karma-jasmine”: “^1.1.0”, “karma-phantomjs-launcher”: “^1.0.4”, “mocha”: “^4.0.1”, “path”: “^0.12.7”, “phantomjs”: “^2.1.7+deprecated”, “supertest”: “^3.0.0”, “webpack”: “^3.10.0” } }
- 这个时候就可以在 webpack.config.js 文件中的 `switch` 输入使用的方法了
//process switch( process.env.NODE_ENV ){ //如果是开发版本 case ‘dev’: module.exports = DevWebpack; break; //如果是上线版本 case ‘prod’: module.exports = ProdWebpack; break; //默认是开发版本 default: module.exports = DevWebpack; break; }
- 因为我们需要在 webpack.dev.js 和 webpack.prod.js 文件中定义哪个是开发版哪个是上线版,需要使用一个插件 [DefinePlugin](https://webpack.js.org/plugins/define-plugin/#usage)<br />
编辑 webpack.dev.js 文件
//这个是开发版本使用的 js 配置文件 //引入 path 模块 const path = require(‘path’); //引入 webpack 模块 const webpack = require(‘webpack’); module.exports = { //入口资源配置 需要编译的文件 entry: { index: [ path.join(dirname, ‘../src/public/scripts/index.es’), path.join(dirname, ‘../src/public/scripts/indexadd.js’) ], tags: [ path.join(dirname, ‘../src/public/scripts/tags.es’) ] }, //资源输出配置 资源输出的路径 output: { //资源输出时的名称 以 MD5 的形式 filename: ‘public/scripts/[name]-[hash:5].js’, //资源输出时的路径 path: path.join(dirname, ‘../build/‘) }, //配置插件 plugins: [ //用这个插件定义 进程 的名称,设置 webpack.config.js 中的 process.env.NODE_ENV 方法对应的值 new webpack.DefinePlugin({ ‘process’:{ ‘NODE_ENV’:’dev’ } }) ] };
编辑 webpack.prod.js 文件
//这个是上线版本使用的 js 配置文件 //引入 path 模块 const path = require(‘path’); //引入 webpack 模块 const webpack = require(‘webpack’); module.exports = { //入口资源配置 需要编译的文件 entry: { index: [ path.join(dirname, ‘../src/public/scripts/index.es’), path.join(dirname, ‘../src/public/scripts/indexadd.js’) ], tags: [ path.join(dirname, ‘../src/public/scripts/tags.es’) ] }, //资源输出配置 资源输出的路径 output: { //资源输出时的名称 以 MD5 的形式 filename: ‘public/scripts/[name]-[hash:5].js’, //资源输出时的路径 path: path.join(dirname, ‘../build/‘) }, //配置插件 plugins: [ //用这个插件定义 进程 的名称,设置 webpack.config.js 中的 process.env.NODE_ENV 方法对应的值 new webpack.DefinePlugin({ ‘process’:{ ‘NODE_ENV’:’prod’ } }) ] };
4. 上面已经将上线和本地的配置文件配置好了,接下来是**监控文件的变化自动刷新浏览器**
- 实现这个功能需要安装 [webpack-livereload-plugin](https://www.npmjs.com/package/webpack-livereload-plugin) 插件
- 装包
npm install —save-dev webpack-livereload-plugin
- 装上之后编辑 webpack.dev.js 和 webpack.prod.js 文件
//同时引入模块 const LiveReloadPlugin = require(‘webpack-livereload-plugin’); //在两个文件的 plugins 中同时新增下面的代码 new LiveReloadPlugin({appendScriptTag:true})
- 这些配置好了之后还要在 google 浏览器中的应用商店里面下载相应的 [livereload](https://chrome.google.com/webstore/search/livereload?utm_source=chrome-ntp-icon) 插件,只有安装了这个插件浏览器才能实现自动刷新,这个是谷歌浏览器开发的插件,是跟 webpack-livereload-plugin 配合使用的<br />
这里原来是添加按钮,现在我添加过了
> 
到了这一步是将**第三个步骤**配置完成了<br />
###4. 使用 Webpack 能够对 CSS、JS 进行编译、压缩、打包、合并,并生成 MD5
1. 编译的话我们首先需要安装一个 [babel-load](https://www.npmjs.com/package/babel-loader) 模块
- 装包
npm install —save-dev babel-loader
- 将插件中的示例代码稍加修改同时复制到 webpack.dev.js 和 webpack.prod.js 文件 中
//这个是与 plugins 同一级的代码 module: { rules: [{ //这个 .es 正则实际上对应的就是 index.es 和 tags.es 文件 test: /.es$/, exclude: /(node_modules|bower_components)/, use: { loader: ‘babel-loader’, options: { presets: [‘es2015’,’stage-0’] } } }] }
2. 在命令行中运行 webpackdev 开发模式下的程序
npm run webpackdev
- 命令行中显示如下就证明执行成功了,没成功就看报错的信息 排错
> 
- 此时查看项目中的 build 目录会发现自动生成了一个 public 目录,结构如下
> 
3. 上面是将 es 编译为了 js ,还需要编译 css 文件,编译 css 需要安装一个 [extract-text-webpack-plugin](https://github.com/webpack-contrib/extract-text-webpack-plugin) 插件
- 装包
npm install —save-dev extract-text-webpack-plugin
- 在 webpack.dev.js 和 webpack.prod.js 文件中 引入该模块
//引入 css 编译插件 const ExtractTextPlugin = require(“extract-text-webpack-plugin”);
- 在 webpack.dev.js 和 webpack.prod.js 文件中同时引入设置该模块的配置
//将下面代码写入 module -> rules 中 { test: /.css$/, use: ExtractTextPlugin.extract({ fallback: “style-loader”, use: “css-loader” }) } //将下面代码写入 plugins 中 new ExtractTextPlugin(“public/css/[name]-[hash:5].css”)
- 因为在上面的配置中用到了 style-loader 和 css-loader 需要装包
npm install —save-dev style-loader css-loader
4. 在命令行中运行 webpackdev 开发模式下的程序
npm run webpackdev
- 会发现并没有编译 css 文件
> 
- 因为实际上 extract-text-webpack-plugin 这个插件是用来分离 css 和 js 文件的,所以我们需要将 css 文件引入到 js 文件中
- 编辑 src -> public -> scripts -> index.es 文件
//在第一行引入 css 文件 extract-text-webpack-plugin
- 引入之后再执行的时候的机制是:在编译 .es 文件时会将其文件中引入的 css 文件代码通过 extract-text-webpack-plugin 插件分离出去 单独去处理的,这个时候再去在命令行中运行 webpackdev 开发模式下的程序
npm run webpackdev
- 在命令行中会发现 css 文件也已经编译成功了
> 
- 在 bulid -> public 目录中自动生成了 css 目录
> 
5. 上面是实现了 js 和 css 的编译,下面需要压缩、合并、打包,压缩的话是不对开发环境(dev)做要求的,不然的话查错不好查,所以只对上线版本进行配置
- js 压缩使用的是 webpack 自带的 [webpack.optimize.UglifyJsPlugin()](https://webpack.js.org/configuration/plugins/#plugins) 方法
- 编辑 webpack.prod.js 文件,压缩只在上线版本中进行
//将下面代码写入 plugins 中 new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false, drop_console: false, } })
6. 在命令行中运行 webpackprod 上线版本的程序
npm run webpackprod
- 可以看到压缩后的 js 明显的变小了
> 
- 方块的是压缩前的 js 文件 箭头的是压缩后的 js 文件
> 
7. 上面是把 js 压缩好了,下面开始压缩 css ,压缩 css 需要安装 [optimize-css-assets-webpack-plugin](https://github.com/NMFR/optimize-css-assets-webpack-plugin) 插件
- 装包
npm install —save-dev optimize-css-assets-webpack-plugin
- 编辑 webpack.prod.js 文件
//引入 css 压缩 模块 const OptimizeCssAssetsPlugin = require(‘optimize-css-assets-webpack-plugin’);
//将下面代码写入 plugins 中 new OptimizeCssAssetsPlugin({ assetNameRegExp: /.optimize.css$/g, cssProcessor: require(‘cssnano’), cssProcessorOptions: { discardComments: { removeAll: true } }, canPrint: true })
8. 在命令行中运行 webpackprod 上线版本的程序
npm run webpackprod
- 可以看到压缩后的 css 明显的变小了
> 
- 此时查看项目中的 bulid -> public -> css 目录中的 css 文件发现已经压缩成功了,此时**第四步骤**就已经完成了
> <br />
###5. 去掉 System.js,利用 Webpack 进行文件引用(同时提取公共文件成立独立 JS 包)
1. 去掉 Stystem.js ,这个 js 之前引用是为了让浏览器识别 js 中的 improt ,而且这个文件必须在服务器端才会执行,所以当时还将项目放到了 htdocs 目录下 测试运行了,现在使用 webpack 已经不需要他了
- 打开 src -> views -> index.html 文件<br />
编辑 index.html 文件
//删掉与 system.js 相关的 引用 js 和下面的