Awesome
San-Hot-Loader
San-Hot-Loader 为基于 Webpack 构建的 San 项目提供热更新功能。能够让 San 项目在调试开发的时候变得更加方便。
安装
代码1-1
$ npm install --save-dev san-hot-loader
配置
您需要在 Webpack 配置文件当中添加 san-hot-loader 相关配置信息。完整的配置信息可参考示例项目当中的 webpack.config.js 配置,其中与 san-hot-loader 有关的配置如下所示:
代码2-1
module.exports = {
// ... 其他的 webpack 配置信息
module: {
rules: [
{
test: /\.js$/,
use: ['san-hot-loader']
}
// ... 其他的 loader 信息
]
}
}
这样,在启动 webpack 进行代码调试的时候,就自动实现了 San 组件与 San-Store 的热更新功能。
需要注意的是,当项目代码使用了 ES7 及以上的语法时,通常需要 babel-loader 将代码进行转换成 ES5 语法,这个转换过程可能会带来额外的 Babel Helper、Polyfill 代码的注入,在这种情况下,san-hot-loader 同时也提供了 babel 插件来实现热更新代码注入:
代码2-2
module.exports = {
// ... 其他的 webpack 配置信息
module: {
rules: [
{
test: /\.js$/,
use: [
// 'san-hot-loader',
{
loader: 'babel-loader',
options: {
plugins: [
// 通过 babel plugin 的形式添加
require.resolve('san-hot-loader/lib/babel-plugin')
]
}
}
]
}
// ... 其他的 loader 信息
]
}
}
使用
在默认情况下,san-hot-loader 自动开启对 San 组件与 San-Store 模块的热更新代码注入,通过自动检测的手段来判断哪些是 San 组件,哪些是 San-Store 模块。
San 组件格式
常规 San 组件格式
一个简单的常规 San 组件文件如下所示:
代码3-1
// app.js
import san from 'san';
export default san.defineComponent({
template: '<p>Hello {{name}}</p>',
initData: function () {
return {name: 'San'};
}
});
也可以采用 class 的方式:
代码3-2
import {Component} from 'san';
export default class App extends Component {
static template = '<p>Hello {{name}}</p>';
initData() {
return {name: 'San'};
}
}
或者采用 ES5 Function Constructor 的方式来定义组件:
代码3-3
var san = require('san');
function App(options) {
san.Component(this, options);
}
san.inherits(App, san.Component);
App.prototype.template = '<p>Hello {{name}}</p>';
App.prototype.initData = function () {
return {name: 'San'};
};
module.exports = App;
基于上述例子不难看出,一个功能完整的组件代码文件应包含以下特征:
- 文件引入
san
模块(import
、require
); - 使用
san
模块所提供的 API(defineComponent、Component)定义组件; - 将定义好的组件作为默认模块导出(
export default
、module.exports
);
以上就是 san-hot-loader 判断文件是否为普通 San 组件的方法。
结合 San-Store 的组件写法
当项目使用 San-Store 时,根据相关文档的说明,需要使用 san-store
提供的 connect 方法将状态源与组件关联起来,如:
代码3-4
import {defineComponent} from 'san';
import {connect} from 'san-store';
import './register-store-actions';
const App = defineComponent({
template: '<p>Hello {{name}}</p>',
initData: function () {
return {name: 'San'};
}
});
// connect 到默认 store
const NewApp = connect.san({name: 'name'})(App);
export default NewApp;
或者:
代码3-5
import {defineComponent} from 'san';
import {connect} from 'san-store';
import store from './store';
const App = defineComponent({
template: '<p>Hello {{name}}</p>',
initData: function () {
return {name: 'San'};
}
});
// connect 到自定义 store
const connector = connect.createConnector(store);
const NewApp = connector({name: 'name'})(App);
export default NewApp;
因此在这种情况下,san-hot-loader 对于满足以下特征的文件,也同样能够识别为 San 组件:
- 文件引入
san-store
(import
、require
); - 使用
san-store
提供的connect.san
或connect.createConnector(store)
得到的方法连接 store 与当前 San 组件获得新的组件; - 将得到的新组件作为默认模块导出(
export default
、module.exports
);
San-Store 模块写法
san-hot-loader 为 san-store 提供了 action 的热更新功能,即在修改 san-store 注册 action 的文件时,store 与组件所保存的状态都不会丢失,并且在触发 action 时自动使用最新的 action。
san-store 提供了默认 store 和自定义 store 两种,因此在支持 san-hot-loader 自动识别的写法上也存在不同。
默认 store
san-store 提供了默认 store 实例,在使用上可以通过 import {store} from 'san-store'
获取该实例对象,调用其 addAction
方法可以完成对默认 store 的 action 注册。
一个简单的对默认 store 实例注册 action 的代码如下所示:
代码3-6
// register-store-actions.js
import {store} from 'san-store';
import {builder} from 'san-update';
store.addAction('increase', function (num) {
builder().set('num', num + 1);
});
store.addAction('decrease', function (num) {
builder().set('num', num - 1);
});
这个文件会被 san-hot-loader 成功识别,并注入热更新代码。该文件可以参考前面的代码3-4 的方式引入到项目当中。
它应具有以下特征:
- 文件引入
san-store
; - 使用
san-store
提供的 store.addAction 方法注册 action; - 文件不存在任何模块导出(
export
、export default
、module.exports
、exports.xxx
);
在这里需要解释一下特征 3。首先,在对默认 store 实例注册 action 之后,可以直接使用 san-store
提供的 connect.san
方法对 San 组件进行关联,因此无需对默认 store 实例做任何的模块导出;其次,热更新的本质是前端异步接收更新后的文件模块,并进行替换,在这里 san-hot-loader 只针对注册 action 的功能进行了替换处理,如果文件存在模块导出,san-hot-loader 无法去跟踪处理相应行为从而会导致热更新出错。所以要求文件不存在任何模块导出。
自定义 store
san-store 提供了自定义 store 的方法来满足开发者多 store 的状态管理需求。一个简单的自定义 store 代码如下所示:
代码3-7
// store.js
import {Store} from 'san-store';
import {builder} from 'san-update';
export default new Store({
initData: {
num: 0
},
actions: {
increase(num) {
return builder().set('num', num + 1);
},
decrease(num) {
return builder().set('num', num - 1);
}
}
});
通过类似前面代码3-5 的写法使用自定义 store,同样地,在修改自定义 store 文件时,同样也能够获得热更新效果。
需要注意的是,在这种自定义 Store 的情况下,只有在修改 actions
部分的代码会实现热更新,当 initData 部分出现改动时,则会直接进行页面重载。
自定义 store 的文件应具有以下特征:
- 文件引入
san-store
; - 使用
san-store
提供的Store
方法实例化自定义 store; - 将自定义 store 以默认模块导出;
特殊注释
前面给出了符合 san-hot-loader 满足热更新自动检测的一些文件写法,在某些情况下可能会存在以下情况:
- 文件不希望被热更新;
- 文件希望被热更新,但是由于没有被自动检测到而热更新失效;
针对这种情况,除了可以采用下文所提到的 san-hot-loader 配置指定之外,还可以直接在书写具体工程代码的时候,通过添加一些特殊的注释进行标记,san-hot-loader 会去检测这些特殊的注释去执行相应的操作。
禁止热更新
在文件头部或尾部添加以下注释,即可达到禁止该文件被热更新的效果:
/* san-hmr disable */
import san from 'san';
export default san.defineComponent({
template: '<div>hello world</div>'
});
开启热更新
热更新包括组件热更新和 San-Store 热更新。
组件热更新需要满足的条件是:当前文件默认导出的模块组件对象。在满足该前提下,可以使用以下注释指定热更新:
// ./component.js
import sanComponentWrapper from './wrapper'
import componentDescriptor from './descriptor';
export default sanComponentWrapper(componentDescriptor);
/* san-hmr component */
其中:
// ./wrapper.js
import san from 'san';
export default function (descriptor) {
return san.defineComponent(descriptor);
}
// ./descriptor.js
export default {
template: '<div>hello world</div>'
}
很明显在上述的示例代码当中 component.js 是无法被 san-hot-loader 自动检测判定为 San 组件文件,因此可以通过注释 /* san-hmr component */
主动告知对该文件执行热更新。
同样,对于 San-Store 也可以使用 /* san-hmr store */
来实现对 Store 的热更新:
import store from './custom-store.js';
store.addAction('increase', )
// ./custom-action.js
import store from './custom-store';
import {builder} from 'san-update';
store.addAction('increase', function (num) {
builder().set('num', num + 1);
});
store.addAction('decrease', function (num) {
builder().set('num', num - 1);
});
/* san-hmr store */
其中:
// .custom-store.js
import {Store} from 'san-store';
export default new Store({
initData: {
num: 0
}
});
在这种情况下,san-hot-loader 无法通过自动检测手段判定 ./custom-action.js 需要执行热更新,因此可以通过 /* san-hmr store */
进行标记。
配置
san-hot-loader 提供一系列配置,通过 webpack loader 的 options
配置项传入,如果使用的是 babel 插件时,在配置上也是一样的:
// webpack loader 配置
module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.js$/,
use: [
{
loader: 'san-hot-loader',
options: {
enable: process.env.NODE_ENV === 'development',
component: {
patterns: [
/\.san\.js$/,
'auto'
]
},
store: {
patterns: [
function (resourcePath) {
return /\.store\.js$/.test(resourcePath);
},
'auto'
]
}
}
}
]
}
]
}
}
// Babel Plugin 配置
module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
plugins: [
[
'san-hot-loader',
{
enable: process.env.NODE_ENV === 'development',
component: {
patterns: [
/\.san\.js$/,
'auto'
]
},
store: {
patterns: [
function (resourcePath) {
return /\.store\.js$/.test(resourcePath);
},
'auto'
]
}
}
]
]
}
}
]
}
]
}
}
接下来将详细介绍 san-hot-loader 配置项的使用方法。
enable
- enable
{boolean}
是否使能该 loader,默认值为 true。一般配合环境变量一起使用,如:
// webpack.config.js
{
loader: 'san-hot-loader',
options: {
enable: process.env.NODE_ENV !== 'production'
}
}
component.enable
- component.enable
{boolean}
是否开启 San 组件热更新,默认值为 true,即默认开启:
{
loader: 'san-hot-loader',
options: {
component: {
enable: false // 关闭 San 组件热更新
}
}
}
component.patterns
- component.patterns
{Array.<Object>}
开启热更新的 San 组件的路径匹配模式,默认值为:[{component: 'auto'}]
,即采取自动检测的模式。
其中 component
的取值除了 'auto'
之外,还可以传入正则表达式和函数来对进行文件路径匹配,比如:
{
loader: 'san-hot-loader',
options: {
component: {
patterns: [
/\.san\.js$/,
function (resourcePath) {
// resourcePath 为当前资源的绝对路径
// return true 则表示匹配成功
return /\.san\.js$/.test(resourcePath);
},
'auto'
]
}
}
}
san-hot-loader 会优先匹配到后缀为 .san.js
的文件时,会直接向该文件注入热更新代码,匹配失败后,再按顺序执行剩余的匹配规则。
store.enable
- store.enable
boolean
是否开启 San-Store 热更新,默认为 true,即默认开启:
{
loader: 'san-hot-loader',
options: {
store: {
enable: false
}
}
}
store.patterns
- store.patterns
{Array.<Object>}
开启热更新的 San Store 模块路径匹配模式,默认值为:['auto']
,即采取自动检测的模式。
无论是使用 san-store 提供的全局 store 还是自定义 store,同样也可以通过正则表达式和函数的方式去指定文件,在规则上与 component 配置一致:
{
loader: 'san-hot-loader',
options: {
store: {
patterns: [
/\.store\.js$/,
function (resourcePath) {
return /\.store\.js$/.test(resourcePath);
},
'auto'
]
}
}
}
示例
项目 examples 提供了一个简单但功能完整的示例,可以将本项目 clone 下来,运行该示例,并尝试修改示例当中的代码来感受热更新为开发所带来的便利。