Giter Site home page Giter Site logo

san-loader's Introduction

San-Loader

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 会将 templatescriptstyle 等标签块当中的内容和属性提取出来,并交给 webpack 分别进行处理。最终单文件对外返回的将是一个普通的 San 组件类,我们可以直接使用它进行 San 组件的各种操作:

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:

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()]
};

在默认情况下,templatescriptstyle 会分别采用 .html.js.css 所对应的 Loader 配置进行处理,当然我们也可以在相应的标签上添加 lang 属性来指定不同的语言处理比如:

<style lang="less">
    @grey: #999;

    div {
        span {
            color: @grey;
        }
    }
</style>

这样,对应的样式模块就可以当成 .less 文件进行处理,只需要配置上相应的 Loader 即可。

// ...
module.exports = {
    // ...
    module: {
        rules: [
            // ...
            {
                test: /\.less$/,
                use: ['style-loader', 'css-loader', 'less-loader']
            }
        ]
    }
};

更加完整的 webpack 配置,可以参考示例:

Options

Name Type Default Description
compileTemplate {'none'|'aPack'|'aNode'} 'none' 将组件的template 编译成aPackaNode默认不编译,详细见下面说明
esModule {Boolean} false san-loader 默认使用 CommonJS 模块语法来生成 JS 模块,将该参数设为 true 可以改用 ES 模块语法
autoAddScriptTag {Boolean} true .san 文件中不含 script 标签时自动添加,默认为true

特殊说明:

compileTemplate:San 组件的string类型的template通过编译可以返回aNode结构,在定义组件的时候,可以直接使用aNode作为 template,这样可以减少了组件的template编译时间,提升了代码的执行效率,但是转成aNode的组件代码相对来说比较大,所以在[email protected]引入的概念的aNode压缩结构aPack使用aPack可以兼顾体积和效率的问题。san-loader 中的compileTemplate就是来指定要不要将组件编译为aPack/aNode如果只想,单文件使用compileTemplate编译成对应的aPack或者aNode,可以直接在template上面写:<template compileTemplate="aPack">。 使用 pug 等预处理模版语言时,compileTemplate 不生效,请使用 san-anode-loader

扩展阅读

单文件写法

template

单文件中 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

script

script 模块必须通过 export default 将组件的 JS 代码导出。在写法上,支持类似 Vue 的写法:

<script>
    export default {
        initData() {
            return {
                name: 'San'
            };
        }
    };
</script>

San-Loader 会自动为导出为普通对象的模块外部自动包上 san.defineComponent 使之成为真正的 San 组件。

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 即可:

<script lang="ts">
    // ...
</script>

这时候需要修改ts-loader配置:

{
    test: /\.ts$/,
    loader: 'ts-loader',
    options: { appendTsSuffixTo: [/\.san$/] }
}

或者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

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 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 参数即可。例如:

// 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 文档

命名 CSS Modules

默认通过<style module></style>方式添加的样式,在组件中会给data添加$style变量。如果组件中有多个style,想区分不同的 style,也可以通过命名的方式定义不同style的变量名,示例如下:

<template>
    <div class="{{$style}}">
        <div class="{{$styleFooter}}"></div>
    </div>
</template>

<style module>
</style>

<style module="styleFooter">
</style>

允许非 CSS Modules

也可以指定部分 style 标签使用 CSS Modules,其他仍然是普通的全局 CSS:

<style module>
    /* 这里是 CSS Modules */
</style>

<style>
    /* 这里是全局 CSS */
</style>

san-loader 会给带 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 不会渗透进入子组件:

<template>
    <div>
        <child-component class="child"></child-component>
    </div>
</template>

<style module>
    .child .foo {
        font-weight: bold;
    }
</style>

但除类名之外的元素名、ID 等会渗透进入子组件,例如下面的 .child span 会作用于 <child-component> 里的 <span>

<template>
    <div>
        <child-component class="child"></child-component>
    </div>
</template>

<style module>
    .child span {
        font-weight: bold;
    }
</style>

Scoped CSS(version 0.3.0 以上)

你可以在 <style> 标签上添加 scoped 属性,此时标签内的 CSS 只作用于当前组件 template 中的元素。编译后的 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>

san-loader's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

san-loader's Issues

支持pug

.san的template指定lang并添加相应的loader后,san-cli仍旧无法编译通过
是不是现在不支持template的lang属性?vue是可以的

配置了 compileTemplate 是 aPack 或 aNode 后,渲染时候组件外层绑定的 style/class/id 没有渲染到 DOM 上

Case

组件 Parent:

<template>
    <div>
        <child class="{{$style.child}}"></child>
    </div>
</template>

<style module>
.child {
    background: red;
}
</style>

组件 Child:

<template>
    <div>child</div>
</template>
</style>

san-loader 配置:

config.module
    .rule('san')
    .test(/\.san$/)
    .use('san-loader')
    .loader('san-loader')
    .options({
        compileTemplate: 'aPack'
    })
    .end();

运行时打断点查看 Child 组件的 aNode:

  • compileTemplate: 'aPack' 时 aNode:
{
  "directives": {
  },
  "props": [
  ],
  "events": [
  ],
  "children": [
    {
      "textExpr": {
        "type": 1,
        "value": "child"
      }
    }
  ],
  "tagName": "div"
}
  • compileTemplate: 'none' 时 aNode:
{
  "directives": {
  },
  "props": [
    {
      "name": "class",
      "expr": {
        "type": 5,
        "expr": {
          "type": 4,
          "paths": [
            {
              "type": 1,
              "value": "class"
            }
          ]
        },
        "filters": [
          {
            "type": 6,
            "args": [
            ],
            "name": {
              "type": 4,
              "paths": [
                {
                  "type": 1,
                  "value": "_class"
                }
              ]
            }
          }
        ]
      }
    },
    {
      "name": "style",
      "expr": {
        "type": 5,
        "expr": {
          "type": 4,
          "paths": [
            {
              "type": 1,
              "value": "style"
            }
          ]
        },
        "filters": [
          {
            "type": 6,
            "args": [
            ],
            "name": {
              "type": 4,
              "paths": [
                {
                  "type": 1,
                  "value": "_style"
                }
              ]
            }
          }
        ]
      }
    },
    {
      "name": "id",
      "expr": {
        "type": 4,
        "paths": [
          {
            "type": 1,
            "value": "id"
          }
        ]
      }
    }
  ],
  "events": [
  ],
  "children": [
    {
      "textExpr": {
        "type": 1,
        "value": "child"
      }
    }
  ],
  "tagName": "div"
}

相比之下,使用 san-loader 不编译 template 比编译 template 的 aNode 多出了 props 的内容:加上了 style / class / id 相关的内容。

分析

san-loader 中会调 san-anode-utils 的 parseTemplate 方法得到 aNode。代码:https://github.com/ecomfe/san-loader/blob/f0fbbef2c0/lib/loader.js#L90
san 运行时里面解析 template 的方法不一样,san 里用的是 parseComponentTemplate。代码:https://github.com/baidu/san/blob/master/src/view/parse-component-template.js#L53
parseComponentTemplate 和 parseTemplate 会多一个 autoFillStyleAndId 的方法,导致组件外层绑定 style/class/id 在渲染时候没有渲染到 DOM 上

README.md例子错误

<script>
    export default {
        data: {
            msg: 'world'
        }
    }
</script>

在script中应该为

    export default {
        initData() {
            return {
                name: 'San'
            }
        }
    }

san-loader不支持动画

san3.4.2以及之前对动画支持不是很好。对比下vue-loader,san-loader在style内使用css keyframe好像是不支持的。

bug

ERROR in Loader /Users/liuyiman/privatespace/san-test/todos-esnext/node_modules/san/dist/san.ssr.js didn't return a function
 @ ./src/main.js 19:16-44

当我使用.san文件的时候,会出现这个错误

node: 8.4.0
npm: 5.6
san: 3.3.2

通过san-cli 编译,CSS Modules 报错

1、通过san-cli创建的demo项目
2、使用的官方示例:

<template>
    <div class="{{$style.wrapper}}"></div>
</template>

<script>
    export default {
        attached() {
            let style = this.data.get('$style');
            console.log(style);
        }
    };
</script>
<!-- 当style标签不添加module时,san serve编译正常 -->
<!-- 添加module后,编译不通过 -->
<style module>
    .wrapper {
        color: black;
    }
</style>

3、在san.config.js中,添加loader配置(直接贴的示例代码):

 configWebpack: config => {
        config.module.rules.push({
            test: /\.css$/,
            use: [
                'style-loader',
                {
                    loader: 'css-loader',
                    options: {
                        modules: {
                            localIdentName: '[local]_[hash:base64:5]'
                        },
                        localsConvention: 'camelCase',
                        sourceMap: true
                    }
                }
            ]
        })
    },

报错:ERROR css-loader#module not set, required by /xxx/xxx/app.san?lang=css&module=&san=&type=style&index=0

Could not build with babel@7 without sourcemap

When devtool is not enabled, webpack will throw an error like following:

ERROR in ./node_modules/babel-loader/lib!./node_modules/san-loader/lib/selector.js?type=script&index=0!./src/views/Home.san
Module build failed: Error: .inputSourceMap must be a boolean, object, or undefined
    at assertInputSourceMap (/local/GitHub/san-study/node_modules/@babel/core/lib/config/validation/option-assertions.js:43:11)
    at /local/GitHub/san-study/node_modules/@babel/core/lib/config/validation/options.js:83:20
    at Array.forEach (<anonymous>)
    at validate (/local/GitHub/san-study/node_modules/@babel/core/lib/config/validation/options.js:61:21)
    at loadConfig (/local/GitHub/san-study/node_modules/@babel/core/lib/config/index.js:37:48)
    at transformSync (/local/GitHub/san-study/node_modules/@babel/core/lib/transform-sync.js:13:36)
    at Object.transform (/local/GitHub/san-study/node_modules/@babel/core/lib/transform.js:20:65)
    at transpile (/local/GitHub/san-study/node_modules/babel-loader/lib/index.js:55:20)
    at Object.module.exports (/local/GitHub/san-study/node_modules/babel-loader/lib/index.js:179:20)
 @ ./src/views/Home.san 5:17-119
 @ ./src/router/index.js
 @ ./src/index.js

Reproduction: https://github.com/JounQin/san-study/blob/master/webpack.config.babel.js
Step:

  1. Run yarn && yarn build, it works with sourcemap.
  2. Then remove devtool option in webpack config, rerun yarn build you will get the error in console.

使用 scoped css 时,scoped id 没有作为 attribute 写回 DOM Element。

问题

在使用 .san 组件时,scoped style 生成了一个 id,这个 id 写入了 style 标签,但没有写回这个 style 对应的 DOM Element。

复现用例

<template>
<span class="foo">foo</span>
</template>

<style scoped>
.foo {
  display: none;
}
</style>

输出

期望输出

<style>
.foo[_s_HASH_OF_COMPONENT] { display: none; }
</style>
...
<span class="foo" _s_HASH_OF_COMPONENT>foo</span>

实际输出

<style>
.foo[_s_HASH_OF_COMPONENT] { display: none; }
</style>
...
<span class="foo">foo</span>

Html-Loader造成的编译异常

异常情况

会有类似Cannot read properties of undefined (reading 'is')的异常,通过debug排查
具体问题出现在以下位置

preheatANode(proto.aNode, this);

    this.tagName = proto.aNode.tagName;
    this.source = typeof options.source === 'string'
        ? parseTemplate(options.source).children[0]
        : options.source;
    // 这个方法传入的this.source为undefined
    preheatANode(this.source);
    proto.aNode._i++;

解决方法,初步设想是loader的问题,最终定位到html-loader与babel-loader

需要将html-loader的esModule设为false即可
如果使用babel-loader, 需要exclude中添加san的过滤

package.json

{
  "dependencies": {
    "san": "^3.11.1"
  },
  "devDependencies": {
    "babel-loader": "^8.0.6",
    "html-loader": "^3.0.1",
    "san-loader": "^0.3.3"
  },
  "browserslist": "> 0.25%, not dead"
}

webpack.config.js

module.exports = (env, argv) => {
  return {
    plugins: [
      new SanLoaderPlugin()
    ],
    module: {
      rules: [
         {
          test: /\.san$/,
          use: [
            { loader: 'san-loader' }
          ]
        },
        {
          test: /\.js?$/,
          // 去除san
          exclude: /(node_modules|bower_components|.*\.san)/,
          loader: 'babel-loader'
        },
        {
          test: /\.html$/,
          loader: 'html-loader',
          options: {
            // 两个参数改为false
            esModule: false,
            minimize: false
          }
        }
      ]
    }
  }
}

如果有碰到类似问题的,希望能有帮助

Scoped CSS 的 descendant, child selectors 优先级

输入

<style scoped>
.a .b .c {
    color: red
}
</style>

输出

1. 向下兼容

.a[913ce72] .b .c {
    color: red
}

场景: slot 与 第三方库

.container[913ce72] .slick-list {}

2. 向上兼容

.a .b .c[913ce72] {
    color: red
}

场景:全局主题

.theme-blue .button[913ce72] {}

问题

12 需要做一个选择, 收集大家的建议

或者,有更优的方案?

希望可以支持强制返回未编译的 template 功能

需求场景

san-ssr-plugin 插件中,会利用 san-loader 的机制获取 template 部分内容。方法是调用 loadModule 方法发起这样一次资源请求:index.san?lang=html&san=&type=template

问题

当 compileTemplate 选项为 aNode 或 aPack 时,会返回编译后的结果。预期是得到未编译的 template 字符串。

需求

希望可以通过增加 query 的方式,强制返回未编译的 template 字符串。例如:index.san?lang=html&san=&type=template&compileTemplate=

目前 query 上的 compileTemplate 参数,只有值为 aPack 或 aNode 的时候有效果:

https://github.com/ecomfe/san-loader/blob/master/lib/loader.js#L84

可以不可以放开?

数据绑定问题?

export default {
data: {
msg: 'world'
}
}
是不是因该是:
export default {
initData() {
return {
name: 'walker'
}
}
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.