fix: update html-loader to ^1.1.0
San-Loader 是基于 webpack 的工具,允许开发者书写 San 单文件的方式来进行组件开发。
<template> <div class="content">Hello {{name}}!</div> </template> <script> export default { initData() { return { name: 'San' }; } }; </script> <style> .content { color: blue; } </style>
San 单文件在写法上与 Vue 类似,San-Loader 会将 template、script、style 等标签块当中的内容和属性提取出来,并交给 webpack 分别进行处理。最终单文件对外返回的将是一个普通的 San 组件类,我们可以直接使用它进行 San 组件的各种操作:
template
script
style
import App from './App.san'; let app = new App(); app.attach(document.body);
通过 npm 进行 San-Loader 的安装:
npm install --save-dev san-loader
然后在 webpack 的配置文件上增加一条规则应用到 .san 文件上,并且增加一个 SanLoaderPlugin:
.san
const SanLoaderPlugin = require('san-loader/lib/plugin'); module.exports = { // ... module: { rules: [ { test: /\.san$/, loader: 'san-loader' } // ... ] }, plugins: [new SanLoaderPlugin()] };
如前面提到,San-Loader 会将单文件的各个部分拆分出来,并交给其他的 Loader 来进行资源处理,因此还需要配置各个模块的处理方法,比如:
const SanLoaderPlugin = require('san-loader/lib/plugin'); module.exports = { // ... module: { rules: [ { test: /\.san$/, loader: 'san-loader' }, { test: /\.js$/, loader: 'babel-loader' }, { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.html$/, loader: 'html-loader' } // ... ] }, plugins: [new SanLoaderPlugin()] };
在默认情况下,template、script、style 会分别采用 .html、.js、.css 所对应的 Loader 配置进行处理,当然我们也可以在相应的标签上添加 lang 属性来指定不同的语言处理比如:
.html
.js
.css
lang
<style lang="less"> @grey: #999; div { span { color: @grey; } } </style>
这样,对应的样式模块就可以当成 .less 文件进行处理,只需要配置上相应的 Loader 即可。
.less
// ... module.exports = { // ... module: { rules: [ // ... { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] } ] } };
更加完整的 webpack 配置,可以参考示例:
compileTemplate
{'none'|'aPack'|'aNode'}
'none'
aPack
aNode
esModule
{Boolean}
false
autoAddScriptTag
true
特殊说明:
compileTemplate:San 组件的string类型的template通过编译可以返回aNode结构,在定义组件的时候,可以直接使用aNode作为 template,这样可以减少了组件的template编译时间,提升了代码的执行效率,但是转成aNode的组件代码相对来说比较大,所以在san@3.9.0引入的概念的aNode压缩结构aPack,使用aPack可以兼顾体积和效率的问题。san-loader 中的compileTemplate就是来指定要不要将组件编译为aPack/aNode。**如果只想,单文件使用compileTemplate编译成对应的aPack或者aNode,可以直接在template上面写:<template compileTemplate="aPack">**。 使用 pug 等预处理模版语言时,compileTemplate 不生效,请使用 san-anode-loader
string
san@3.9.0
<template compileTemplate="aPack">
pug
单文件中 template 模块的主要作用是提供一种更为便捷的方式来书写组件的 template 字符串。在配置 webpack 的时候,需要对 template 部分配置 raw-loader、html-loader 等等。其中如果 template 当中需要使用到图片、字体文件,建议采用 html-loader 配合 url-loader 的形式完成相关配置。例如:
<template> <div> <img src="../assets/logo.png" /> </div> </template>
则需要在 webpack 配置文件当中增加如下配置:
module.exports = { module: { rules: [ // ... { test: /\.html$/, loader: 'html-loader' }, { test: /\.png$/, loader: 'url-loader' } ] } };
template 部分可以省略不写,直接在 script 模块当中定义也是可以的:
<script> export default { template: '<div>{{name}}</div>' }; </script>
template 模块也支持通过 src 标签引入 template 文件:
<template src="./component-template.html"></template>
注意:html-loader 最新版本在生产环境(production)会默认开启minimize=true,会导致 san 解析 template 失败,所以使用 html-loader 的时候建议开启minimize=false。
minimize=true
minimize=false
script 模块必须通过 export default 将组件的 JS 代码导出。在写法上,支持类似 Vue 的写法:
export default
<script> export default { initData() { return { name: 'San' }; } }; </script>
San-Loader 会自动为导出为普通对象的模块外部自动包上 san.defineComponent 使之成为真正的 San 组件。
san.defineComponent
import script from './App.san?san&type=script&lang=js'; import san from 'san'; // ... export default san.defineComponent(script);
我们也可以通过 class 的方式:
<script> import san from 'san'; export default class App extends san.Component { initData() { return { name: 'San' }; } } </script>
也可以配合 san-store 一起使用,比如:
<script> import san from 'san'; import {store, connect} from 'san-store'; import {builder} from 'san-update'; // ... export default connect.san({ name: 'user.name' })( san.defineComponent({ // ... }) ); </script>
总之在写法上与普通的 San 组件不存在太大区别,区别的地方只在于 template 和 style 的部分可以放到别的模块里进行书写。
当组件不依赖数据和计算的时候,script 块可以省略不写。
与 template 相似,script 模块也可以通过定义 src 属性导入相应的组件代码:
<script src="./component-script.js"></script>
在默认情况下,script 模块的内容会被当成 .js 文件进行处理,如改成 TypeScript 的话,可以通过在 script 标签上添加属性 lang="ts" 将该模块标记为 .ts 文件,然后自行在 webpack 配置文件当中添加对 .ts 文件的处理 Loader 即可:
lang="ts"
.ts
<script lang="ts"> // ... </script>
这时候需要修改ts-loader配置:
ts-loader
{ test: /\.ts$/, loader: 'ts-loader', options: { appendTsSuffixTo: [/\.san$/] } }
或者babel-loader的配置:
babel-loader
{ test: /\.ts$/, use: [ { loader: 'babel-loader', options: { plugins: [ require.resolve('@babel/plugin-proposal-class-properties'), require.resolve('san-hot-loader/lib/babel-plugin') ], presets: [ [ require.resolve('@babel/preset-env'), { targets: { browsers: '> 1%, last 2 versions' }, modules: false } ], // 下面配置 allExtensions [require.resolve('@babel/preset-typescript'), {allExtensions: true}] ] } } ] }
style 模块用来书写组件的样式,在用法上与 template、script 类似,例如:
<style> .parent { color: red; } .parent .children { color: green; } </style>
在默认情况下,style 模块的内容会被当成 .css 文件处理,我们可以通过修改 lang 属性来指定文件类型。同时与 template、script 存在区别的地方在于,style 模块允许写多个,因此下面写的 style 模块都是有效的,全都会作用到当前的组件上:
<template><!-- 组件模板 --></template> <script> /* 组件 script */ </script> <style> /* 写普通 css */ .parent .children { color: green; } </style> <style lang="less"> /* 写 less */ @grey: #999; .parent { .children { background: @grey; } } </style> <!-- 引入外部 stylus 样式文件 --> <style src="./component-style.styl"></style>
CSS Modules 是一个流行的用于模块化和组合 CSS 的系统,san-loader 提供了与 css-loader 的集成以支持 CSS Modules 的特性。在模板中可以这样写:
<template> <div class="{{$style.wrapper}}"></div> </template> <script> export default { attached() { let style = this.data.get('$style'); console.log(style); } }; </script> <style module> .wrapper { color: black; } </style>
如果要对所有文件生效,在上面的 webpack 配置示例中给 css-loader 添加 modules 参数即可。例如:
modules
// webpack.config.js 省略上下文 rules: [ { test: /\.css$/, use: [ 'style-loader', { loader: 'css-loader', options: { modules: { localIdentName: '[local]_[hash:base64:5]' }, localsConvention: 'camelCase', sourceMap: true } } ] } ];
其中 localIdentName 用来指定编译后的类名,在开发环境请使用 '[hash:base64]'; localsConvention 是在模板和 JavaScript 中引用的名称,默认是不转换,'camelCase' 是把类名转换为驼峰风格。详情请参考:css-loader 文档。
localIdentName
'[hash:base64]'
localsConvention
'camelCase'
默认通过<style module></style>方式添加的样式,在组件中会给data添加$style变量。如果组件中有多个style,想区分不同的 style,也可以通过命名的方式定义不同style的变量名,示例如下:
<style module></style>
data
$style
<template> <div class="{{$style}}"> <div class="{{$styleFooter}}"></div> </div> </template> <style module> </style> <style module="styleFooter"> </style>
也可以指定部分 style 标签使用 CSS Modules,其他仍然是普通的全局 CSS:
<style module> /* 这里是 CSS Modules */ </style> <style> /* 这里是全局 CSS */ </style>
san-loader 会给带 module 的 <style> 添加对应的 resourceQuery,所以你可以这样配置:
module
<style>
resourceQuery
// webpack.config.js 省略上下文 rules: [ { test: /\.css$/, oneOf: [ // 这里匹配 `<style module>` { resourceQuery: /module/, use: [ 'style-loader', { loader: 'css-loader', options: { modules: { localIdentName: '[local]_[hash:base64:5]' }, localsConvention: 'camelCase', sourceMap: true } } ] }, // 这里匹配 `<style>` { use: [ { loader: 'style-loader' }, { loader: 'css-loader', options: { sourceMap: true } } ] } ] } ];
你也可以把 CSS Modules 和 LESS 等预处理器一起使用,添加对应的 loader 即可。比如:
// webpack.config.js 省略上下文 rules: [ { test: /\.less$/, oneOf: [ // 这里匹配 `<style lang="less" module>` { resourceQuery: /module/, use: [ 'style-loader', { loader: 'css-loader', options: { modules: { localIdentName: '[local]_[hash:base64:5]' }, localsConvention: 'camelCase', sourceMap: true } }, { loader: 'less-loader', options: { sourceMap: true } } ] } // 这里匹配 `<style lang="less">` // ... ] } ];
CSS Modules 可以在使用 slot 时使用(会被编译到随机的类名):
<template> <div> <child-component> <span class="{{$style.bold}}">foo</span> </child-component> </div> </template> <style module> .bold { font-weight: bold; } </style>
也可以设置子组件的根元素样式(会被正确编译到随机类名):
<template> <div> <child-component class="child"></child-component> </div> </template> <style module> .child { font-weight: bold; } </style>
但父组件无法覆盖子组件的内部类的样式,比如子组件内存在类名 .foo,父组件里的 .child .foo 不会渗透进入子组件:
.foo
.child .foo
<template> <div> <child-component class="child"></child-component> </div> </template> <style module> .child .foo { font-weight: bold; } </style>
但除类名之外的元素名、ID 等会渗透进入子组件,例如下面的 .child span 会作用于 <child-component> 里的 <span>:
.child span
<child-component>
<span>
<template> <div> <child-component class="child"></child-component> </div> </template> <style module> .child span { font-weight: bold; } </style>
你可以在 <style> 标签上添加 scoped 属性,此时标签内的 CSS 只作用于当前组件 template 中的元素。编译后的 html 会添加 data-s-${hash} 属性。举例:
scoped
html
data-s-${hash}
<template> <div> <h1>red</h1> </div> </template> <style scoped> h1 { color: red; } </style>
浏览器中会表现为
... <head> <style> h1[data-s-2dad60b2] { color: red; } </style> </head> <body> <h1>normal black</h1> ... <div data-s-2dad60b2> <h1 data-s-2dad60b2>red</h1> </div> </body>
版权所有:中国计算机学会技术支持:开源发展技术委员会 京ICP备13000930号-9 京公网安备 11010802032778号
San-Loader
San-Loader 是基于 webpack 的工具,允许开发者书写 San 单文件的方式来进行组件开发。
San 单文件在写法上与 Vue 类似,San-Loader 会将
template、script、style等标签块当中的内容和属性提取出来,并交给 webpack 分别进行处理。最终单文件对外返回的将是一个普通的 San 组件类,我们可以直接使用它进行 San 组件的各种操作:使用方法
通过 npm 进行 San-Loader 的安装:
然后在 webpack 的配置文件上增加一条规则应用到
.san文件上,并且增加一个 SanLoaderPlugin:如前面提到,San-Loader 会将单文件的各个部分拆分出来,并交给其他的 Loader 来进行资源处理,因此还需要配置各个模块的处理方法,比如:
在默认情况下,
template、script、style会分别采用.html、.js、.css所对应的 Loader 配置进行处理,当然我们也可以在相应的标签上添加lang属性来指定不同的语言处理比如:这样,对应的样式模块就可以当成
.less文件进行处理,只需要配置上相应的 Loader 即可。更加完整的 webpack 配置,可以参考示例:
Options
compileTemplate{'none'|'aPack'|'aNode'}'none'template编译成aPack、aNode,默认不编译,详细见下面说明esModule{Boolean}falseautoAddScriptTag{Boolean}true.san文件中不含script标签时自动添加,默认为true特殊说明:
扩展阅读
单文件写法
template
单文件中
template模块的主要作用是提供一种更为便捷的方式来书写组件的 template 字符串。在配置 webpack 的时候,需要对 template 部分配置 raw-loader、html-loader 等等。其中如果 template 当中需要使用到图片、字体文件,建议采用 html-loader 配合 url-loader 的形式完成相关配置。例如:则需要在 webpack 配置文件当中增加如下配置:
template 部分可以省略不写,直接在 script 模块当中定义也是可以的:
template 模块也支持通过 src 标签引入 template 文件:
script
script 模块必须通过
export default将组件的 JS 代码导出。在写法上,支持类似 Vue 的写法:San-Loader 会自动为导出为普通对象的模块外部自动包上
san.defineComponent使之成为真正的 San 组件。我们也可以通过 class 的方式:
也可以配合 san-store 一起使用,比如:
总之在写法上与普通的 San 组件不存在太大区别,区别的地方只在于 template 和 style 的部分可以放到别的模块里进行书写。
当组件不依赖数据和计算的时候,script 块可以省略不写。
与 template 相似,script 模块也可以通过定义 src 属性导入相应的组件代码:
在默认情况下,script 模块的内容会被当成
.js文件进行处理,如改成 TypeScript 的话,可以通过在 script 标签上添加属性lang="ts"将该模块标记为.ts文件,然后自行在 webpack 配置文件当中添加对.ts文件的处理 Loader 即可:这时候需要修改
ts-loader配置:或者
babel-loader的配置:style
style 模块用来书写组件的样式,在用法上与 template、script 类似,例如:
在默认情况下,style 模块的内容会被当成
.css文件处理,我们可以通过修改lang属性来指定文件类型。同时与 template、script 存在区别的地方在于,style 模块允许写多个,因此下面写的 style 模块都是有效的,全都会作用到当前的组件上:CSS Modules
基本使用
CSS Modules 是一个流行的用于模块化和组合 CSS 的系统,san-loader 提供了与 css-loader 的集成以支持 CSS Modules 的特性。在模板中可以这样写:
如果要对所有文件生效,在上面的 webpack 配置示例中给 css-loader 添加
modules参数即可。例如:其中
localIdentName用来指定编译后的类名,在开发环境请使用'[hash:base64]';localsConvention是在模板和 JavaScript 中引用的名称,默认是不转换,'camelCase'是把类名转换为驼峰风格。详情请参考:css-loader 文档。命名 CSS Modules
默认通过
<style module></style>方式添加的样式,在组件中会给data添加$style变量。如果组件中有多个style,想区分不同的 style,也可以通过命名的方式定义不同style的变量名,示例如下:允许非 CSS Modules
也可以指定部分 style 标签使用 CSS Modules,其他仍然是普通的全局 CSS:
san-loader 会给带
module的<style>添加对应的resourceQuery,所以你可以这样配置:和预处理器一起使用
你也可以把 CSS Modules 和 LESS 等预处理器一起使用,添加对应的 loader 即可。比如:
一些有用的用例
CSS Modules 可以在使用 slot 时使用(会被编译到随机的类名):
也可以设置子组件的根元素样式(会被正确编译到随机类名):
但父组件无法覆盖子组件的内部类的样式,比如子组件内存在类名
.foo,父组件里的.child .foo不会渗透进入子组件:但除类名之外的元素名、ID 等会渗透进入子组件,例如下面的
.child span会作用于<child-component>里的<span>:Scoped CSS(version 0.3.0 以上)
你可以在
<style>标签上添加scoped属性,此时标签内的 CSS 只作用于当前组件 template 中的元素。编译后的html会添加data-s-${hash}属性。举例:浏览器中会表现为