Photogallery技术演进第2步

构建工具升级

因为要拆分代码,便于管理,需要使用importexport,因此必须要引入webpack这样的打包工具到gulp中,使用webpack-stream,具体使用方法和其他的gulp插件类似,在pipe在这样插入就行了.pipe(webpack()),配置方式和webpack一样。(webpack中引入babel-loader的过程就不赘述了)引入webpack后,开发流程和一起类似,gulp启动测试服务器,使用webpack通过entry.js打包代码,CSS和JSON相关流程不变。release时,增加了minify的流程,让js流程后的代码再压缩一遍。

另外,引入babel后,可以用ES6语法改写gulpfile.js。最后的gulpfile.babel.js像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import gulp from 'gulp';
import rename from 'gulp-rename';
import uglify from 'gulp-uglify';
import cleanCSS from 'gulp-clean-css';
import jsonminify from 'gulp-jsonminify';
import webserver from 'gulp-webserver';
import webpack from 'webpack-stream';

gulp.task('js', function () {
return gulp.src('src/index.js')
.pipe(webpack({
module: {
rules: [
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
]
}
}))
.pipe(rename("index.js"))
.pipe(gulp.dest('dist'));
});

gulp.task('minify', function () {
return gulp.src('dist/index.js')
.pipe(uglify())
.pipe(rename("index.min.js"))
.pipe(gulp.dest("dist"));
})

gulp.task('css', function () {
return gulp.src(['src/index.css'])
.pipe(cleanCSS({compatibility: 'ie8'}))
.pipe(gulp.dest('dist'));
});

gulp.task('json', function () {
return gulp.src('src/meta*.json')
.pipe(jsonminify())
.pipe(gulp.dest('dist'))
});

gulp.task('webserver', function() {
gulp.src('./')
.pipe(webserver({
livereload: true,
directoryListing: true,
open: true
}));
});

gulp.task('watch', function() {
gulp.watch(['src/*.js', 'src/**/*.js', 'src/**/*.vue'], ['js']);
gulp.watch('src/*.css', ['css']);
gulp.watch('src/*.json', ['json']);
})

gulp.task('assets', ['json', 'css', 'js']);
gulp.task('default', ['assets', 'webserver', 'watch']);
gulp.task("release", ['assets', 'minify']);

使用单文件组件

引入webpack后,开始高高兴兴地分模块拆分代码,却发现分组件使用Vue时,不是单纯地定义组件配置信息,然后传给入口组件就行。必须要引入全家桶,vue-loader等工具,文件不得不用.vue这样的形式组织(现在开始觉得React组件的组织比Vue舒服了)。本来使用单文件的形式就是想尽量精简,可随着功能逐渐健全,看来重构也是避免不了的啊。在vue-loader的介绍里,居然还要通过vue-cli来大一统,但是我一是想维持项目尽量轻量精简,使用gulp的工具链;二是项目已经写了很久了,全部迁移过去成本有些大。于是,通过vue-cli新建样本项目,对着package.jsonwebpack.config.js一抄了事。

加上种种.vue文件的相关配置,gulp.babel.js最后长下面这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ...
gulp.task('js', function () {
return gulp.src('src/index.js')
.pipe(webpack({
module: {
rules: [
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" },
{ test: /\.vue$/, loader: 'vue-loader'},
{ test: /\.(png|jpg|gif|svg)$/, loader: 'file-loader', options: { name: '[name].[ext]?[hash]' } }
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
},
extensions: ['*', '.js', '.vue', '.json']
}
}))
.pipe(rename("index.js"))
.pipe(gulp.dest('dist'));
});
// ...

下面需要开始拆分代码了。根据React/Vue这样框架通常的设计经验,需要下面一些组成:

  • actions 存储状态管理的动作
  • components 存储相互解耦的”dumb”组件,最好和业务无关
  • constants 存储全局常量
  • containers 存储组织components的业务容器组件
  • entry 存储入口文件
  • helper 存储工具函数
  • reducers 存储状态管理的reducers
  • settings 存储全局配置,通常用来初始化store
  • store 存储全局状态
  • templates 存储引入js的html文件

我的项目比较简单,一没有状态管理(后面复杂了之后可能会引入😂),二只有三个组件,只要上面的components, constants, containers, helper的就够用了。最后src下的文件目录大概像下面这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
├── App.vue
├── components
│   └── column
│   └── Column.vue
├── constants
│   └── index.js
├── containers
│   ├── info
│   │   └── Info.vue
│   └── wall
│   └── Wall.vue
├── helper
│   └── utils.js
├── index.css
└── index.js

在拆分时遇到了一些数据需要从最外层透传到子组件的情况,如res, tag_list, tag_keys。不过他们是只读的,而且数目很少,所以并不需要状态管理,只用通过props传下去就行了。

新功能

重构完之后,终于可以写新功能了。新功能主要是增加两个伪路由,方便页面的分享(这个需求我之前遇到过几次了)。页面是spa类型的,所以前端路由可以采用hash或history H5 API来实现。同时也有许多在这个基础上了前端路由库,提供一站式解决方案。我的需求目前其实不需要完整的路由方案:

  • 图片详情页可以分享
  • 搜索结果可以分享

因此,设计上使用hash的方案,对于图片详情页,用!开头,后接图片序号。对于搜索结果页,则没有开头的!,仅使用/隔开每一个搜索关键字。对hash的读写上,没有什么困难的地方:

  • App.vue根据hash注入对应的数据,更改默认视图,对于图片详情页,更改展示组件
  • :切换组件时,记录当前数据到location.hash,方便直接复制链接分享

后面的计划

现在网站还是有点单调了。只能自娱自乐,没有互动。后面应该会考虑在每张图片接入Disqus的问题。