Skip to content

Commit

Permalink
add some comments
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoiver committed Aug 3, 2017
1 parent bd9903e commit 1cb1083
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 23 deletions.
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,29 @@ 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)

## 使用方法

安装:
```bash
npm install vue-skeleton-webpack-plugin
```

运行测试用例:
```bash
npm run test
```

在 webpack 中引入插件:
```js
// webpack.conf.js
Expand Down
12 changes: 4 additions & 8 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ const DEFAULT_PLUGIN_OPTIONS = {
insertAfter: '<div id="app">'
};

const DEFAULT_LOADER_OPTIONS = {
importTemplate: 'import [nameCap] from \'@/pages/[nameCap].vue\';',
routePathTemplate: '/skeleton-[name]',
insertAfter: 'routes: ['
};

class SkeletonPlugin {

constructor(options = {}) {
Expand All @@ -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);
Expand All @@ -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`;

Expand All @@ -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)
});
}
}
Expand Down
37 changes: 26 additions & 11 deletions src/loader.js
Original file line number Diff line number Diff line change
@@ -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 <panyuqi@baidu.com>
*/

Expand All @@ -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;
});

Expand Down
6 changes: 4 additions & 2 deletions src/ssr.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/**
* @file ssr
* @desc Use vue ssr to render skeleton components. The result contains html and css.
* @author panyuqi <panyuqi@baidu.com>
*/

Expand All @@ -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
}));
Expand All @@ -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);
Expand Down

0 comments on commit 1cb1083

Please sign in to comment.