{"id":29926664,"url":"https://github.com/ecomfe/san-loader","last_synced_at":"2025-08-02T12:43:06.716Z","repository":{"id":37382146,"uuid":"67659698","full_name":"ecomfe/san-loader","owner":"ecomfe","description":"San-Loader 是基于 webpack 的工具，允许开发者书写 San 单文件的方式来进行组件开发。","archived":false,"fork":false,"pushed_at":"2023-10-30T10:14:54.000Z","size":160,"stargazers_count":24,"open_issues_count":2,"forks_count":21,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-05-25T08:05:21.976Z","etag":null,"topics":["loader","san","webpack"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ecomfe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2016-09-08T02:09:12.000Z","updated_at":"2024-12-04T13:14:13.000Z","dependencies_parsed_at":"2023-01-23T14:46:45.824Z","dependency_job_id":"5a35cd49-e5bb-4146-abe0-c300ef2f8d97","html_url":"https://github.com/ecomfe/san-loader","commit_stats":{"total_commits":95,"total_committers":18,"mean_commits":5.277777777777778,"dds":0.7578947368421053,"last_synced_commit":"cd5d9f09e7c45c8e0d2d741f063129c8df631d6a"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ecomfe/san-loader","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecomfe%2Fsan-loader","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecomfe%2Fsan-loader/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecomfe%2Fsan-loader/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecomfe%2Fsan-loader/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ecomfe","download_url":"https://codeload.github.com/ecomfe/san-loader/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecomfe%2Fsan-loader/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268392180,"owners_count":24243297,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-02T02:00:12.353Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["loader","san","webpack"],"created_at":"2025-08-02T12:42:30.366Z","updated_at":"2025-08-02T12:43:06.624Z","avatar_url":"https://github.com/ecomfe.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# San-Loader\n\nSan-Loader 是基于 webpack 的工具，允许开发者书写 San\n单文件的方式来进行组件开发。\n\n```html\n\u003ctemplate\u003e\n    \u003cdiv class=\"content\"\u003eHello {{name}}!\u003c/div\u003e\n\u003c/template\u003e\n\n\u003cscript\u003e\n    export default {\n        initData() {\n            return {\n                name: 'San'\n            };\n        }\n    };\n\u003c/script\u003e\n\n\u003cstyle\u003e\n    .content {\n        color: blue;\n    }\n\u003c/style\u003e\n```\n\nSan 单文件在写法上与 Vue 类似，San-Loader 会将 `template`、`script`、`style` 等标签块当中的内容和属性提取出来，并交给 webpack 分别进行处理。最终单文件对外返回的将是一个普通的 San 组件类，我们可以直接使用它进行 San 组件的各种操作：\n\n```js\nimport App from './App.san';\nlet app = new App();\napp.attach(document.body);\n```\n\n## 使用方法\n\n通过 npm 进行 San-Loader 的安装：\n\n```shell\nnpm install --save-dev san-loader\n```\n\n然后在 webpack 的配置文件上增加一条规则应用到 `.san` 文件上，并且增加一个 SanLoaderPlugin：\n\n```js\nconst SanLoaderPlugin = require('san-loader/lib/plugin');\n\nmodule.exports = {\n    // ...\n    module: {\n        rules: [\n            {\n                test: /\\.san$/,\n                loader: 'san-loader'\n            }\n            // ...\n        ]\n    },\n    plugins: [new SanLoaderPlugin()]\n};\n```\n\n如前面提到，San-Loader 会将单文件的各个部分拆分出来，并交给其他的 Loader 来进行资源处理，因此还需要配置各个模块的处理方法，比如：\n\n```js\nconst SanLoaderPlugin = require('san-loader/lib/plugin');\n\nmodule.exports = {\n    // ...\n    module: {\n        rules: [\n            {\n                test: /\\.san$/,\n                loader: 'san-loader'\n            },\n            {\n                test: /\\.js$/,\n                loader: 'babel-loader'\n            },\n            {\n                test: /\\.css$/,\n                use: ['style-loader', 'css-loader']\n            },\n            {\n                test: /\\.html$/,\n                loader: 'html-loader'\n            }\n            // ...\n        ]\n    },\n    plugins: [new SanLoaderPlugin()]\n};\n```\n\n在默认情况下，`template`、`script`、`style` 会分别采用 `.html`、`.js`、`.css` 所对应的 Loader 配置进行处理，当然我们也可以在相应的标签上添加 `lang` 属性来指定不同的语言处理比如：\n\n```html\n\u003cstyle lang=\"less\"\u003e\n    @grey: #999;\n\n    div {\n        span {\n            color: @grey;\n        }\n    }\n\u003c/style\u003e\n```\n\n这样，对应的样式模块就可以当成 `.less` 文件进行处理，只需要配置上相应的 Loader 即可。\n\n```js\n// ...\nmodule.exports = {\n    // ...\n    module: {\n        rules: [\n            // ...\n            {\n                test: /\\.less$/,\n                use: ['style-loader', 'css-loader', 'less-loader']\n            }\n        ]\n    }\n};\n```\n\n更加完整的 webpack 配置，可以参考示例：\n\n- [San-Loader Webpack HMR 配置实例](https://github.com/ecomfe/san-loader/blob/master/examples/hmr/webpack.config.js)\n- [San-Loader Webpack Minimal](https://github.com/ecomfe/san-loader/blob/master/examples/minimal/webpack.config.js)\n\n## Options\n\n|       Name        |            Type            | Default  | Description                                                               |\n| :---------------: | :------------------------: | :------: | :------------------------------------------------------------------------ |\n| `compileTemplate` | \u003ccode\u003e{'none'\u0026#124;'aPack'\u0026#124;'aNode'}\u003c/code\u003e | `'none'` | 将组件的`template` 编译成`aPack`、`aNode`，**默认不编译**，详细见下面说明 |\n| `esModule` | `{Boolean}` | `false` | san-loader 默认使用 CommonJS 模块语法来生成 JS 模块，将该参数设为 true 可以改用 ES 模块语法 |\n| `autoAddScriptTag` | `{Boolean}` | `true` | `.san` 文件中不含 `script` 标签时自动添加，默认为`true` |\n\n**特殊说明：**\n\n\u003e `compileTemplate`：San 组件的`string`类型的`template`通过编译可以返回[aNode](https://github.com/baidu/san/blob/master/doc/anode.md)结构，在定义组件的时候，可以直接使用`aNode`作为 template，这样可以减少了组件的`template`编译时间，提升了代码的执行效率，但是转成`aNode`的组件代码相对来说比较大，所以在`san@3.9.0`引入的概念的`aNode`压缩结构`aPack`，**使用`aPack`可以兼顾体积和效率的问题**。san-loader 中的`compileTemplate`就是来指定要不要将组件编译为`aPack`/`aNode`。**如果只想，单文件使用`compileTemplate`编译成对应的`aPack`或者`aNode`，可以直接在`template`上面写：`\u003ctemplate compileTemplate=\"aPack\"\u003e`**。\n\u003e 使用 `pug` 等预处理模版语言时，`compileTemplate` 不生效，请使用 [san-anode-loader](https://github.com/vanishcode/san-anode-loader)\n\n### 扩展阅读\n\n- [aNode 结构设计](https://github.com/baidu/san/blob/master/doc/anode.md)\n- [aPack: aNode 压缩结构设计](https://github.com/baidu/san/blob/master/doc/anode-pack.md)\n\n## 单文件写法\n\n### template\n\n单文件中 `template` 模块的主要作用是提供一种更为便捷的方式来书写组件的 template 字符串。在配置 webpack 的时候，需要对 template 部分配置 raw-loader、html-loader 等等。其中如果 template 当中需要使用到图片、字体文件，建议采用 html-loader 配合 url-loader 的形式完成相关配置。例如：\n\n```html\n\u003ctemplate\u003e\n    \u003cdiv\u003e\n        \u003cimg src=\"../assets/logo.png\" /\u003e\n    \u003c/div\u003e\n\u003c/template\u003e\n```\n\n则需要在 webpack 配置文件当中增加如下配置：\n\n```js\nmodule.exports = {\n    module: {\n        rules: [\n            // ...\n            {\n                test: /\\.html$/,\n                loader: 'html-loader'\n            },\n            {\n                test: /\\.png$/,\n                loader: 'url-loader'\n            }\n        ]\n    }\n};\n```\n\ntemplate 部分可以省略不写，直接在 script 模块当中定义也是可以的：\n\n```html\n\u003cscript\u003e\n    export default {\n        template: '\u003cdiv\u003e{{name}}\u003c/div\u003e'\n    };\n\u003c/script\u003e\n```\n\ntemplate 模块也支持通过 src 标签引入 template 文件：\n\n```html\n\u003ctemplate src=\"./component-template.html\"\u003e\u003c/template\u003e\n```\n\n\u003e 注意：html-loader 最新版本在生产环境（[production](https://github.com/webpack-contrib/html-loader/blob/master/src/index.js#L38-L41)）会默认开启`minimize=true`，会导致 san 解析 template 失败，所以使用 html-loader 的时候建议开启`minimize=false`。\n\n### script\n\nscript 模块必须通过 `export default` 将组件的 JS 代码导出。在写法上，支持类似 Vue 的写法：\n\n```html\n\u003cscript\u003e\n    export default {\n        initData() {\n            return {\n                name: 'San'\n            };\n        }\n    };\n\u003c/script\u003e\n```\n\nSan-Loader 会自动为导出为普通对象的模块外部自动包上 `san.defineComponent` 使之成为真正的 San 组件。\n\n```js\nimport script from './App.san?san\u0026type=script\u0026lang=js';\nimport san from 'san';\n// ...\nexport default san.defineComponent(script);\n```\n\n我们也可以通过 class 的方式：\n\n```html\n\u003cscript\u003e\n    import san from 'san';\n    export default class App extends san.Component {\n        initData() {\n            return {\n                name: 'San'\n            };\n        }\n    }\n\u003c/script\u003e\n```\n\n也可以配合 san-store 一起使用，比如：\n\n```html\n\u003cscript\u003e\n    import san from 'san';\n    import {store, connect} from 'san-store';\n    import {builder} from 'san-update';\n\n    // ...\n    export default connect.san({\n        name: 'user.name'\n    })(\n        san.defineComponent({\n            // ...\n        })\n    );\n\u003c/script\u003e\n```\n\n总之在写法上与普通的 San 组件不存在太大区别，区别的地方只在于 template 和 style 的部分可以放到别的模块里进行书写。\n\n当组件不依赖数据和计算的时候，script 块可以省略不写。\n\n与 template 相似，script 模块也可以通过定义 src 属性导入相应的组件代码：\n\n```html\n\u003cscript src=\"./component-script.js\"\u003e\u003c/script\u003e\n```\n\n在默认情况下，script 模块的内容会被当成 `.js` 文件进行处理，如改成 TypeScript 的话，可以通过在 script 标签上添加属性 `lang=\"ts\"` 将该模块标记为 `.ts` 文件，然后自行在 webpack 配置文件当中添加对 `.ts` 文件的处理 Loader 即可：\n\n```html\n\u003cscript lang=\"ts\"\u003e\n    // ...\n\u003c/script\u003e\n```\n\n这时候需要修改`ts-loader`配置：\n\n```js\n{\n    test: /\\.ts$/,\n    loader: 'ts-loader',\n    options: { appendTsSuffixTo: [/\\.san$/] }\n}\n```\n\n或者`babel-loader`的配置：\n\n```js\n{\n    test: /\\.ts$/,\n    use: [\n        {\n            loader: 'babel-loader',\n            options: {\n                plugins: [\n                    require.resolve('@babel/plugin-proposal-class-properties'),\n                    require.resolve('san-hot-loader/lib/babel-plugin')\n                ],\n                presets: [\n                    [\n                        require.resolve('@babel/preset-env'),\n                        {\n                            targets: {\n                                browsers: '\u003e 1%, last 2 versions'\n                            },\n                            modules: false\n                        }\n\n                    ],\n                    // 下面配置 allExtensions\n                    [require.resolve('@babel/preset-typescript'), {allExtensions: true}]\n                ]\n            }\n        }\n    ]\n}\n```\n\n### style\n\nstyle 模块用来书写组件的样式，在用法上与 template、script 类似，例如：\n\n```html\n\u003cstyle\u003e\n    .parent {\n        color: red;\n    }\n\n    .parent .children {\n        color: green;\n    }\n\u003c/style\u003e\n```\n\n在默认情况下，style 模块的内容会被当成 `.css` 文件处理，我们可以通过修改 `lang` 属性来指定文件类型。同时与 template、script 存在区别的地方在于，style 模块允许写多个，因此下面写的 style 模块都是有效的，全都会作用到当前的组件上：\n\n```html\n\u003ctemplate\u003e\u003c!-- 组件模板  --\u003e\u003c/template\u003e\n\u003cscript\u003e\n    /* 组件 script */\n\u003c/script\u003e\n\u003cstyle\u003e\n    /* 写普通 css */\n    .parent .children {\n        color: green;\n    }\n\u003c/style\u003e\n\n\u003cstyle lang=\"less\"\u003e\n    /* 写 less */\n    @grey: #999;\n    .parent {\n        .children {\n            background: @grey;\n        }\n    }\n\u003c/style\u003e\n\u003c!-- 引入外部 stylus 样式文件 --\u003e\n\u003cstyle src=\"./component-style.styl\"\u003e\u003c/style\u003e\n```\n\n## CSS Modules\n\n### 基本使用\n\n[CSS Modules][css-modules] 是一个流行的用于模块化和组合 CSS 的系统，san-loader 提供了与 css-loader 的集成以支持 CSS Modules 的特性。在模板中可以这样写：\n\n```html\n\u003ctemplate\u003e\n    \u003cdiv class=\"{{$style.wrapper}}\"\u003e\u003c/div\u003e\n\u003c/template\u003e\n\n\u003cscript\u003e\n    export default {\n        attached() {\n            let style = this.data.get('$style');\n            console.log(style);\n        }\n    };\n\u003c/script\u003e\n\n\u003cstyle module\u003e\n    .wrapper {\n        color: black;\n    }\n\u003c/style\u003e\n```\n\n如果要对所有文件生效，在上面的 webpack 配置示例中给 css-loader 添加 `modules` 参数即可。例如：\n\n```javascript\n// webpack.config.js 省略上下文\nrules: [\n    {\n        test: /\\.css$/,\n        use: [\n            'style-loader',\n            {\n                loader: 'css-loader',\n                options: {\n                    modules: {\n                        localIdentName: '[local]_[hash:base64:5]'\n                    },\n                    localsConvention: 'camelCase',\n                    sourceMap: true\n                }\n            }\n        ]\n    }\n];\n```\n\n其中 `localIdentName` 用来指定编译后的类名，在开发环境请使用 `'[hash:base64]'`；\n`localsConvention` 是在模板和 JavaScript 中引用的名称，默认是不转换，`'camelCase'` 是把类名转换为驼峰风格。详情请参考：[css-loader 文档][css-loader]。\n\n### 命名 CSS Modules\n\n默认通过`\u003cstyle module\u003e\u003c/style\u003e`方式添加的样式，在组件中会给`data`添加`$style`变量。如果组件中有多个`style`，想区分不同的 style，也可以通过命名的方式定义不同`style`的变量名，示例如下：\n\n``` html\n\u003ctemplate\u003e\n    \u003cdiv class=\"{{$style}}\"\u003e\n        \u003cdiv class=\"{{$styleFooter}}\"\u003e\u003c/div\u003e\n    \u003c/div\u003e\n\u003c/template\u003e\n\n\u003cstyle module\u003e\n\u003c/style\u003e\n\n\u003cstyle module=\"styleFooter\"\u003e\n\u003c/style\u003e\n```\n\n### 允许非 CSS Modules\n\n也可以指定部分 style 标签使用 CSS Modules，其他仍然是普通的全局 CSS：\n\n```html\n\u003cstyle module\u003e\n    /* 这里是 CSS Modules */\n\u003c/style\u003e\n\n\u003cstyle\u003e\n    /* 这里是全局 CSS */\n\u003c/style\u003e\n```\n\nsan-loader 会给带 `module` 的 `\u003cstyle\u003e` 添加对应的 `resourceQuery`，所以你可以这样配置：\n\n```javascript\n// webpack.config.js 省略上下文\nrules: [\n    {\n        test: /\\.css$/,\n        oneOf: [\n            // 这里匹配 `\u003cstyle module\u003e`\n            {\n                resourceQuery: /module/,\n                use: [\n                    'style-loader',\n                    {\n                        loader: 'css-loader',\n                        options: {\n                            modules: {\n                                localIdentName: '[local]_[hash:base64:5]'\n                            },\n                            localsConvention: 'camelCase',\n                            sourceMap: true\n                        }\n                    }\n                ]\n            },\n            // 这里匹配 `\u003cstyle\u003e`\n            {\n                use: [\n                    {\n                        loader: 'style-loader'\n                    },\n                    {\n                        loader: 'css-loader',\n                        options: {\n                            sourceMap: true\n                        }\n                    }\n                ]\n            }\n        ]\n    }\n];\n```\n\n### 和预处理器一起使用\n\n你也可以把 CSS Modules 和 LESS 等预处理器一起使用，添加对应的 loader 即可。比如：\n\n```javascript\n// webpack.config.js 省略上下文\nrules: [\n    {\n        test: /\\.less$/,\n        oneOf: [\n            // 这里匹配 `\u003cstyle lang=\"less\" module\u003e`\n            {\n                resourceQuery: /module/,\n                use: [\n                    'style-loader',\n                    {\n                        loader: 'css-loader',\n                        options: {\n                            modules: {\n                                localIdentName: '[local]_[hash:base64:5]'\n                            },\n                            localsConvention: 'camelCase',\n                            sourceMap: true\n                        }\n                    },\n                    {\n                        loader: 'less-loader',\n                        options: {\n                            sourceMap: true\n                        }\n                    }\n                ]\n            }\n            // 这里匹配 `\u003cstyle lang=\"less\"\u003e`\n            // ...\n        ]\n    }\n];\n```\n\n### 一些有用的用例\n\nCSS Modules 可以在使用 slot 时使用（会被编译到随机的类名）：\n\n```html\n\u003ctemplate\u003e\n    \u003cdiv\u003e\n        \u003cchild-component\u003e\n            \u003cspan class=\"{{$style.bold}}\"\u003efoo\u003c/span\u003e\n        \u003c/child-component\u003e\n    \u003c/div\u003e\n\u003c/template\u003e\n\n\u003cstyle module\u003e\n    .bold {\n        font-weight: bold;\n    }\n\u003c/style\u003e\n```\n\n也可以设置子组件的根元素样式（会被正确编译到随机类名）：\n\n```html\n\u003ctemplate\u003e\n    \u003cdiv\u003e\n        \u003cchild-component class=\"child\"\u003e\u003c/child-component\u003e\n    \u003c/div\u003e\n\u003c/template\u003e\n\n\u003cstyle module\u003e\n    .child {\n        font-weight: bold;\n    }\n\u003c/style\u003e\n```\n\n但父组件无法覆盖子组件的内部类的样式，比如子组件内存在类名 `.foo`，父组件里的 `.child .foo` 不会渗透进入子组件：\n\n```html\n\u003ctemplate\u003e\n    \u003cdiv\u003e\n        \u003cchild-component class=\"child\"\u003e\u003c/child-component\u003e\n    \u003c/div\u003e\n\u003c/template\u003e\n\n\u003cstyle module\u003e\n    .child .foo {\n        font-weight: bold;\n    }\n\u003c/style\u003e\n```\n\n但除类名之外的元素名、ID 等会渗透进入子组件，例如下面的 `.child span` 会作用于 `\u003cchild-component\u003e` 里的 `\u003cspan\u003e`：\n\n```html\n\u003ctemplate\u003e\n    \u003cdiv\u003e\n        \u003cchild-component class=\"child\"\u003e\u003c/child-component\u003e\n    \u003c/div\u003e\n\u003c/template\u003e\n\n\u003cstyle module\u003e\n    .child span {\n        font-weight: bold;\n    }\n\u003c/style\u003e\n```\n\n[css-modules]: https://github.com/css-modules/css-modules\n[css-loader]: https://github.com/webpack-contrib/css-loader#localsconvention\n\n## Scoped CSS（version 0.3.0 以上）\n\n你可以在 `\u003cstyle\u003e` 标签上添加 `scoped` 属性，此时标签内的 CSS 只作用于当前组件 template 中的元素。编译后的 `html` 会添加 `data-s-${hash}` 属性。举例：\n\n```html\n\u003ctemplate\u003e\n    \u003cdiv\u003e\n        \u003ch1\u003ered\u003c/h1\u003e\n    \u003c/div\u003e\n\u003c/template\u003e\n\n\u003cstyle scoped\u003e\n    h1 {\n        color: red;\n    }\n\u003c/style\u003e\n```\n\n浏览器中会表现为\n\n```html\n...\n\u003chead\u003e\n    \u003cstyle\u003e\n        h1[data-s-2dad60b2] {\n            color: red;\n        }\n    \u003c/style\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n    \u003ch1\u003enormal black\u003c/h1\u003e\n    ...\n    \u003cdiv data-s-2dad60b2\u003e\n        \u003ch1 data-s-2dad60b2\u003ered\u003c/h1\u003e\n    \u003c/div\u003e\n\u003c/body\u003e\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fecomfe%2Fsan-loader","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fecomfe%2Fsan-loader","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fecomfe%2Fsan-loader/lists"}