diff --git a/README.md b/README.md
index e9b4dfd..9c65b9e 100644
--- a/README.md
+++ b/README.md
@@ -6,15 +6,17 @@ vue-skeleton-webpack-plugin
[![NPM](https://nodei.co/npm/vue-skeleton-webpack-plugin.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/vue-skeleton-webpack-plugin/)
-基于 vue 的 webpack 插件,为单页/多页应用生成 skeleton,提升首屏展示体验。
+这是一个基于 Vue 的 webpack 插件,为单页/多页应用生成骨架屏 skeleton,减少白屏时间,在页面完全渲染之前提升用户感知体验。
## 基本实现
-参考了[Ele.me的这篇文章](https://medium.com/elemefe/upgrading-ele-me-to-progressive-web-app-2a446832e509),
+参考了[饿了么的 PWA 升级实践](https://huangxuan.me/2017/07/12/upgrading-eleme-to-pwa/)一文,
使用服务端渲染在构建时渲染 skeleton 组件,将 DOM 和样式内联到最终输出的 html 中。
另外,为了开发时调试方便,会将对应路由写入`router.js`中,可通过`/skeleton`路由访问。
+插件具体实现可参考[我的这篇文章](https://xiaoiver.github.io/coding/2017/07/30/%E4%B8%BAvue%E9%A1%B9%E7%9B%AE%E6%B7%BB%E5%8A%A0%E9%AA%A8%E6%9E%B6%E5%B1%8F.html)
+
## 使用方法
安装:
@@ -22,6 +24,11 @@ vue-skeleton-webpack-plugin
npm install vue-skeleton-webpack-plugin
```
+运行测试用例:
+```bash
+npm run test
+```
+
在 webpack 中引入插件:
```js
// webpack.conf.js
diff --git a/src/index.js b/src/index.js
index 7ae9864..6c76d44 100644
--- a/src/index.js
+++ b/src/index.js
@@ -13,12 +13,6 @@ const DEFAULT_PLUGIN_OPTIONS = {
insertAfter: '
'
};
-const DEFAULT_LOADER_OPTIONS = {
- importTemplate: 'import [nameCap] from \'@/pages/[nameCap].vue\';',
- routePathTemplate: '/skeleton-[name]',
- insertAfter: 'routes: ['
-};
-
class SkeletonPlugin {
constructor(options = {}) {
@@ -43,10 +37,12 @@ class SkeletonPlugin {
compiler.plugin('compilation', compilation => {
+ // add listener for html-webpack-plugin
compilation.plugin('html-webpack-plugin-before-html-processing', (htmlPluginData, callback) => {
let usedChunks = htmlPluginData.plugin.options.chunks;
let entryKey;
+
// find current processing entry
if (Array.isArray(usedChunks)) {
entryKey = Object.keys(skeletonEntries);
@@ -56,6 +52,7 @@ class SkeletonPlugin {
entryKey = 'app';
}
+ // set current entry & output in webpack config
webpackConfig.entry = skeletonEntries[entryKey];
webpackConfig.output.filename = `skeleton-${entryKey}.js`;
@@ -76,8 +73,7 @@ class SkeletonPlugin {
static loader(ruleOptions = {}) {
return Object.assign(ruleOptions, {
loader: require.resolve('./loader'),
- options: Object.assign({}, DEFAULT_LOADER_OPTIONS,
- Object.assign({}, ruleOptions.options))
+ options: Object.assign({}, ruleOptions.options)
});
}
}
diff --git a/src/loader.js b/src/loader.js
index 6d58d88..1028068 100644
--- a/src/loader.js
+++ b/src/loader.js
@@ -1,5 +1,7 @@
/**
* @file loader
+ * @desc Insert route of skeleton into router.js, so that developer can
+ * visit route path in dev mode to debug skeleton components
* @author panyuqi
*/
@@ -8,34 +10,47 @@
const loaderUtils = require('loader-utils');
const insertAt = require('./util').insertAt;
+const DEFAULT_LOADER_OPTIONS = {
+ // template of importing skeleton component
+ importTemplate: 'import [nameCap] from \'@/pages/[nameCap].vue\';',
+ // template of route path
+ routePathTemplate: '/skeleton-[name]',
+ // position to insert route object in router.js file
+ insertAfter: 'routes: ['
+};
+
const ENTRY_NAME_HOLDER = /\[name\]/gi;
const ENTRY_NAME_CAP_HOLDER = /\[nameCap\]/gi;
module.exports = function (source) {
- const options = loaderUtils.getOptions(this);
+ const options = Object.assign({}, DEFAULT_LOADER_OPTIONS, loaderUtils.getOptions(this));
let {entry, importTemplate, routePathTemplate, insertAfter} = options;
- // position to insert in router.js
+ // find position to insert in router.js
let routesPos = source.indexOf(insertAfter) + insertAfter.length;
- if (!Array.isArray(entry)) {
- entry = [entry];
- }
+ entry = Array.isArray(entry) ? entry : [entry];
entry.forEach(entryName => {
- // capitalize first letter
+ // capitalize first letter in entryName eg.skeleton -> Skeleton
let entryCap = entryName.replace(/([a-z])(.*)/, (w, firstLetter, rest) => firstLetter.toUpperCase() + rest);
- // route path
- let skeletonRoutePath = routePathTemplate.replace(ENTRY_NAME_HOLDER, entryName)
- .replace(ENTRY_NAME_CAP_HOLDER, entryCap);
- let importExpression = importTemplate.replace(ENTRY_NAME_HOLDER, entryName)
- .replace(ENTRY_NAME_CAP_HOLDER, entryCap);
+
+ // replace placeholder in routeTpl and importTpl
+ let [skeletonRoutePath, importExpression] = [routePathTemplate, importTemplate]
+ .map(pathStr => pathStr.replace(ENTRY_NAME_HOLDER, entryName)
+ .replace(ENTRY_NAME_CAP_HOLDER, entryCap));
+
+ // route object to insert
let routeExpression = `{
path: '${skeletonRoutePath}',
name: '${entryName}-skeleton',
component: ${entryCap}
},`;
+
+ // insert route object into routes array
source = insertAt(source, routeExpression, routesPos);
+
+ // insert import sentence in the head
source += importExpression;
});
diff --git a/src/ssr.js b/src/ssr.js
index d32ee08..2adbc89 100644
--- a/src/ssr.js
+++ b/src/ssr.js
@@ -1,5 +1,6 @@
/**
* @file ssr
+ * @desc Use vue ssr to render skeleton components. The result contains html and css.
* @author panyuqi
*/
@@ -20,7 +21,7 @@ module.exports = serverWebpackConfig => new Promise((resolve, reject) => {
console.log(`Generate skeleton for ${outputBasename}...`);
- // extract css
+ // extract css into a single file
serverWebpackConfig.plugins.push(new ExtractTextPlugin({
filename: outputCssBasename
}));
@@ -47,8 +48,9 @@ module.exports = serverWebpackConfig => new Promise((resolve, reject) => {
let bundle = mfs.readFileSync(outputPath, 'utf-8');
let skeletonCss = mfs.readFileSync(outputCssPath, 'utf-8');
+ // create renderer with bundle
let renderer = createBundleRenderer(bundle);
- // ssr skeleton
+ // use vue ssr to render skeleton
renderer.renderToString({}, (err, skeletonHtml) => {
if (err) {
reject(err);