├── .babelrc
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .travis.yml
├── LICENSE
├── Logo.png
├── README.md
├── illustration.png
├── index.js
├── lib
├── addScopeIDToCSS
│ ├── index.js
│ └── scopeIdPlugin.js
├── buildTemplate.js
├── core.js
├── createOutput.js
├── findScopedSelectors.js
├── genScopeID.js
├── parsePage.js
├── rewriteTemplate.js
└── utils
│ └── reportError.js
├── package.json
├── test
├── buildTemplate.test.js
├── createOutput.test.js
├── findScopedSelectors.test.js
├── fixture
│ ├── componentB
│ │ ├── componentB.css
│ │ ├── componentB.js
│ │ └── componentB.tpl
│ ├── include
│ │ ├── componentA
│ │ │ ├── componentA.css
│ │ │ ├── componentA.js
│ │ │ └── componentA.tpl
│ │ └── componentD
│ │ │ ├── componentD.css
│ │ │ ├── componentD.js
│ │ │ └── componentD.tpl
│ ├── pageC.entry.js
│ ├── pageC
│ │ ├── pageC.css
│ │ ├── pageC.js
│ │ └── pageC.tpl
│ ├── pageE.entry.js
│ ├── pageE
│ │ ├── pageE.css
│ │ ├── pageE.js
│ │ └── pageE.tpl
│ └── scopeCompo
│ │ ├── scopeCompo.css
│ │ └── scopeCompo.tpl
├── genScopeID.test.js
├── parsePage.test.js
├── setScope.test.js
├── testInWebpack.test.js
└── util
│ ├── buildOption.normal.js
│ ├── components.normal.js
│ └── webpack.config.normal.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015"]
3 | }
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | test/*
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "extends": "airbnb-base",
3 | "env": {
4 | "browser": true,
5 | "node": true
6 | }
7 | };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | test/assets
3 | test/assetsCoreTest
4 | test/coverage
5 | .idea
6 | jest_0
7 | package-lock.json
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "10"
4 |
5 | after_success:
6 | - npm i coveralls
7 | - cat ./test/coverage/lcov.info | coveralls
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Nicholas Lee
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholaslee119/webpack-component-loader/06a4bd8a625e72afce2ae6734f58bf4df5135877/Logo.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [![NPM][npm]][npm-url]
2 | [![Tests][build]][build-url]
3 | [![Deps][deps]][deps-url]
4 | [![Coverage][cover]][cover-url]
5 |
6 |
11 |
12 | # webpack-component-loader
13 | A webpack loader to componentify CSS/JS/HTML without framework
14 |
15 | 
16 |
17 | ## Conception
18 |
19 | 简体中文: [如何在没有前端框架的情况下实现组件化](https://juejin.im/post/59df7e76f265da431e15c4fe)
20 |
21 | 日本語: [Frontend Framework無しでComponent化を導入する](https://qiita.com/nicholaslee/items/6c7643b9e6be12531ac3)
22 |
23 | ## highlight features
24 |
25 | 1. Scoped CSS
26 |
27 | ## Install
28 | ```javascript
29 |
30 | $npm install webpack-component-loader
31 |
32 | ```
33 | ## Test
34 | ```
35 | $npm test
36 | ```
37 |
38 | ## Usage
39 |
40 | ### webpack
41 |
42 | ```js
43 |
44 | import {extractor, injector, addScopeAttr} from 'webpack-component-loader-smarty-parser';
45 | // or create the injector, extractor or addScopeAttr by yourself
46 | function extractor (template) {
47 | // extract the including component path from the plain text of template
48 | }
49 | function injector (template, component, buildOption) {
50 | // inject the url of assets to template
51 | }
52 | function addScopeAttr (template, component) {
53 | // add data-s-[hash] to tags in template for scope css, and must return Promise type
54 | return new Promise();
55 | }
56 |
57 | module.exports = {
58 | entry: {
59 | entryA: './test/fixture/entryA.js',
60 | entryB: './test/fixture/entryB.js'
61 | },
62 | output: {
63 | path: path.resolve(__dirname, "../assets/"),
64 | filename : 'js/[name].js',
65 | chunkFilename: 'js/[name].chunk.js',
66 | },
67 | module: {
68 | rules: [
69 | {
70 | test : /\.tpl?$/,
71 | exclude: /(node_modules)/,
72 | use: [
73 | {
74 | loader: 'webpack-component-loader',
75 | options: {
76 | isCodeSplit: false,
77 | extractor,
78 | injector,
79 | addScopeAttr,
80 | ext: '.tpl',
81 | srcPath : path.resolve(__dirname, '.'),
82 | builtTemplatePath : path.resolve(__dirname, '../assets/templates'),
83 | },
84 | },
85 | ],
86 | },
87 | {
88 | test: /\.css$/,
89 | exclude: /(node_modules)/,
90 | enforce: "post",
91 | use: ExtractTextPlugin.extract({
92 | fallback: "style-loader",
93 | use: "css-loader"
94 | })
95 | }
96 | ],
97 | },
98 | plugins: [
99 | new ExtractTextPlugin({
100 | filename: "css/[name].css",
101 | // allChunks: true
102 | }),
103 | new webpack.optimize.CommonsChunkPlugin({
104 | name: "commons",
105 | filename: "js/commons.js",
106 | })
107 | ]
108 | }
109 | ```
110 |
111 | ## Ecosystem
112 |
113 | | Name | Status | Description |
114 | |:----:|:------:|:-----------:|
115 | |[component-smarty-parser][smarty]|[![npm][smarty-badge]][smarty-npm]| Parser to extract and inject smarty template|
116 | |[component-pug-parser][pug]|[![npm][pug-badge]][pug-npm]| Parser to extract and inject pug template |
117 |
118 | [smarty]: https://github.com/nicholaslee119/webpack-component-loader-smarty-parser
119 | [smarty-badge]: https://img.shields.io/npm/v/webpack-component-loader-smarty-parser.svg
120 | [smarty-npm]: https://npmjs.com/package/webpack-component-loader-smarty-parser
121 |
122 | [pug]: https://github.com/nicholaslee119/webpack-component-loader-smarty-parser
123 | [pug-badge]: https://img.shields.io/npm/v/webpack-component-loader-smarty-parser.svg
124 | [pug-npm]: https://npmjs.com/package/webpack-component-loader-smarty-parser
125 |
126 | ## RoadMap
127 |
128 | [RoadMap](https://github.com/nicholaslee119/webpack-component-loader/projects/1)
129 |
130 | ## License
131 |
132 | [MIT](http://opensource.org/licenses/MIT)
133 |
134 |
135 |
136 | [npm]: https://img.shields.io/npm/v/webpack-component-loader.svg
137 | [npm-url]: https://www.npmjs.com/package/webpack-component-loader
138 |
139 | [deps]: https://david-dm.org/nicholaslee119/webpack-component-loader/dev-status.svg
140 | [deps-url]: https://david-dm.org/nicholaslee119/webpack-component-loader?type=dev
141 |
142 | [cover]: https://coveralls.io/repos/github/nicholaslee119/webpack-component-loader/badge.svg?branch=master
143 | [cover-url]: https://coveralls.io/github/nicholaslee119/webpack-component-loader?branch=master
144 |
145 |
146 | [build]: https://travis-ci.org/nicholaslee119/webpack-component-loader.svg?branch=master
147 | [build-url]: https://travis-ci.org/nicholaslee119/webpack-component-loader
148 |
--------------------------------------------------------------------------------
/illustration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicholaslee119/webpack-component-loader/06a4bd8a625e72afce2ae6734f58bf4df5135877/illustration.png
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const isValidPath = require('is-valid-path');
2 |
3 | const loaderUtils = require('loader-utils');
4 | const core = require('./lib/core');
5 | const reportError = require('./lib/utils/reportError');
6 |
7 | function checkPath(...urls) {
8 | return urls.every((url) => {
9 | const checkRes = typeof url === 'string' && isValidPath(url);
10 | if (!checkRes) {
11 | reportError(`${url} is not validate path`);
12 | }
13 | return checkRes;
14 | });
15 | }
16 |
17 | function checkSource(source) {
18 | const res = typeof source === 'string' && source;
19 | if (!res) reportError('something wrong with the source');
20 | }
21 |
22 | function checkFunction(extractor) {
23 | const res = typeof extractor === 'function';
24 | if (!res) reportError('something wrong with the extractor');
25 | }
26 |
27 |
28 | module.exports = function loaderEntry(source) {
29 | this.cacheable();
30 | const options = loaderUtils.getOptions(this);
31 |
32 | checkPath(options.srcPath, options.builtTemplatePath, this.resourcePath);
33 | checkFunction(options.extractor, options.addScopeAttr);
34 | checkFunction(options.addScopeAttr);
35 | checkFunction(options.injector);
36 | checkSource(source);
37 |
38 | const buildOption = {
39 | ext: options.ext,
40 | srcPath: options.srcPath,
41 | builtTemplatePath: options.builtTemplatePath,
42 | builtAssetsPublicPath: options.builtAssetsPublicPath,
43 | isCodeSplit: options.isCodeSplit,
44 | extractor: options.extractor,
45 | injector: options.injector,
46 | addScopeAttr: options.addScopeAttr,
47 | currentPagePath: this.resourcePath,
48 | publicPath: this.options.output.publicPath || '',
49 | };
50 |
51 | return core(source, buildOption);
52 | };
53 |
--------------------------------------------------------------------------------
/lib/addScopeIDToCSS/index.js:
--------------------------------------------------------------------------------
1 | // this is a entry of webpack-loader
2 | const postcss = require('postcss');
3 | const loaderUtils = require('loader-utils');
4 | const scopeId = require('./scopeIdPlugin');
5 |
6 | module.exports = function addScopeIDToCSS(css) {
7 | this.cacheable();
8 | const cb = this.async();
9 | const query = loaderUtils.getOptions(this) || {};
10 | const id = query.scopeID;
11 |
12 | const options = {
13 | from: this.resourcePath,
14 | to: this.resourcePath,
15 | map: false,
16 | };
17 |
18 | postcss([scopeId({ id })])
19 | .process(css, options)
20 | .then(res => cb(null, res.css));
21 | };
22 |
--------------------------------------------------------------------------------
/lib/addScopeIDToCSS/scopeIdPlugin.js:
--------------------------------------------------------------------------------
1 | const postcss = require('postcss');
2 | const selectorParser = require('postcss-selector-parser');
3 |
4 | module.exports = postcss.plugin('add-id', ({ id }) => (cssNodes) => {
5 |
6 | const isScoped = cssNodes.some(node => node.type === 'comment' && node.text === 'scoped');
7 |
8 | if (isScoped) {
9 | cssNodes.some((node) => {
10 | if (!node.selector) {
11 | return false;
12 | }
13 | node.selector = selectorParser((selectors) => {
14 | selectors.each((selector) => {
15 | let insertNode = null;
16 | selector.each((n) => {
17 | insertNode = n;
18 | })
19 | selector.insertAfter(insertNode, selectorParser.attribute({
20 | attribute: `data-s-${id}`,
21 | }));
22 | });
23 | }).process(node.selector).result;
24 | return false;
25 | });
26 | }
27 | });
28 |
--------------------------------------------------------------------------------
/lib/buildTemplate.js:
--------------------------------------------------------------------------------
1 | const reportError = require('./utils/reportError');
2 |
3 | const rewriteTemplate = require('./rewriteTemplate');
4 |
5 | module.exports = function buildTemplate(components, buildOption) {
6 | if (!Array.isArray(components)) {
7 | reportError('something wrong with building Template: components is not an array');
8 | }
9 | components.forEach(rewriteTemplate, buildOption);
10 | };
11 |
--------------------------------------------------------------------------------
/lib/core.js:
--------------------------------------------------------------------------------
1 | const parsePage = require('./parsePage');
2 | const createOutput = require('./createOutput');
3 | const buildTemplate = require('./buildTemplate');
4 | const genScopeID = require('./genScopeID');
5 | const findScopedSelectors = require('./findScopedSelectors');
6 |
7 | module.exports = function core(source, buildOption) {
8 | const components = parsePage(source, buildOption);
9 | genScopeID(components);
10 | findScopedSelectors(components, buildOption);
11 | buildTemplate(components, buildOption);
12 | return createOutput(components, buildOption);
13 | }
14 |
--------------------------------------------------------------------------------
/lib/createOutput.js:
--------------------------------------------------------------------------------
1 | const fsx = require('fs-extra');
2 | const path = require('path');
3 |
4 | const reportError = require('./utils/reportError');
5 |
6 | module.exports = function createOutput(components, buildOption) {
7 | let output = '';
8 | if (!Array.isArray(components)) {
9 | reportError('the component is not an array');
10 | }
11 |
12 | components.forEach((component) => {
13 | const compoPath = path.resolve(buildOption.srcPath, component.dir, component.name, component.name);
14 | const addScopeIDToCSSPath = path.resolve(__dirname, './addScopeIDToCSS/index');
15 | if (fsx.existsSync(`${compoPath}.js`)) {
16 | if (buildOption.isCodeSplit) {
17 | output += `import('babel-loader!${compoPath}.js');\n`;
18 | } else {
19 | output += `require('babel-loader!${compoPath}.js');\n`;
20 | }
21 | }
22 | if (fsx.existsSync(`${compoPath}.css`)) {
23 | output += `require('${addScopeIDToCSSPath}?{"scopeID":"${component.scopeID}"}!${compoPath}.css');\n`;
24 | }
25 | });
26 |
27 | return output;
28 | }
--------------------------------------------------------------------------------
/lib/findScopedSelectors.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fsx = require('fs-extra');
3 |
4 | // TODO: should improve, maybe deal with postcss
5 | function findSelectors(component, buildOption) {
6 | const cssFilePath = path.resolve(buildOption.srcPath, component.dir, component.name, `${component.name}.css`);
7 | const css = fsx.readFileSync(cssFilePath, 'utf8');
8 | const selectorNameReg = new RegExp("([^\r\n,{}]+)(,(?=[^}]*{)|\s*{)", 'g');
9 | if (css.indexOf('/* scoped */') === -1) return [];
10 | const res = css.match(selectorNameReg);
11 | return res.map(item => item.slice(1, item.length - 1).trim());
12 | }
13 |
14 | module.exports = function findScopedSelectors(components, buildOption) {
15 | components.forEach((component) => {
16 | component.scopedSelectors = findSelectors(component, buildOption);
17 | return component;
18 | });
19 | }
20 |
--------------------------------------------------------------------------------
/lib/genScopeID.js:
--------------------------------------------------------------------------------
1 | // TODO: make it more reliable
2 | const hash = require('hash-sum');
3 |
4 | const cache = Object.create(null);
5 |
6 | function genID(component) {
7 | const filePath = `${component.dir}/${component.base}`;
8 | const res = cache[filePath] || (cache[filePath] = hash(filePath));
9 | return res;
10 | }
11 |
12 | module.exports = function genScopeID(components) {
13 | components.forEach((component) => {
14 | component.scopeID = genID(component);
15 | return component;
16 | });
17 | };
18 |
--------------------------------------------------------------------------------
/lib/parsePage.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fsx = require('fs-extra');
3 |
4 | const reportError = require('./utils/reportError');
5 |
6 | let components;
7 | const pushedComponents = new Map();
8 |
9 | function recursiveParse(source, buildOption) {
10 | let includes;
11 | try {
12 | includes = buildOption.extractor(source);
13 | } catch (e) {
14 | reportError(`something wrong with the extractor: ${e}`);
15 | }
16 | if (!Array.isArray(includes)) {
17 | reportError('the result of extractor is not an array');
18 | }
19 | includes.forEach((componentPath) => {
20 | if (pushedComponents.get(componentPath)) return;
21 | pushedComponents.set(componentPath, true);
22 | const parsed = path.parse(componentPath);
23 | const parsedPath = path.resolve(buildOption.srcPath, parsed.dir, parsed.name, parsed.base);
24 | if (fsx.existsSync(parsedPath)) {
25 | components.push(parsed);
26 | recursiveParse(fsx.readFileSync(parsedPath, 'utf8'), buildOption);
27 | }
28 | });
29 | }
30 |
31 | module.exports = function parsePage(source, buildOption) {
32 | components = [];
33 | recursiveParse(source, buildOption);
34 | const pageSelf = path.parse(buildOption.currentPagePath);
35 | pageSelf.dir = pageSelf.dir.replace(pageSelf.name, '').replace(`${buildOption.srcPath}/`, '');
36 | pageSelf.root = '';
37 | pageSelf.pageFlag = true;
38 | components.push(pageSelf);
39 | return components;
40 | };
41 |
--------------------------------------------------------------------------------
/lib/rewriteTemplate.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fsx = require('fs-extra');
3 | const reportError = require('./utils/reportError');
4 |
5 | module.exports = async function rewriteTemplate(component) {
6 | const buildOption = this;
7 | const templatePath = path.resolve(buildOption.srcPath, component.dir, component.name, `${component.base}`);
8 | if (fsx.existsSync(templatePath)) {
9 | try {
10 | const template = fsx.readFileSync(templatePath, 'utf8');
11 | let res = await buildOption.addScopeAttr(template, component);
12 | if (component.pageFlag) res = buildOption.injector(res, component, buildOption);
13 | fsx.ensureDirSync(path.resolve(buildOption.builtTemplatePath, component.dir));
14 | fsx.writeFileSync(path.resolve(buildOption.builtTemplatePath, component.dir, `${component.base}`), res);
15 | } catch (e) {
16 | reportError(e);
17 | }
18 | } else {
19 | reportError(`something wrong with building Template: ${templatePath} is non existence`);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/utils/reportError.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = function reportError(message) {
3 | throw new Error(`[webpack-component-loader]: ${message}\n`);
4 | }
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webpack-component-loader",
3 | "version": "1.0.10",
4 | "description": "A webpack loader to componentify CSS/JS/HTML without framework",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "jest"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/nicholaslee119/webpack-component-loader.git"
12 | },
13 | "keywords": [
14 | "webpack",
15 | "loader",
16 | "component"
17 | ],
18 | "author": "nicholas lee",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/nicholaslee119/webpack-component-loader/issues"
22 | },
23 | "homepage": "https://github.com/nicholaslee119/webpack-component-loader#readme",
24 | "devDependencies": {
25 | "babel-jest": "^21.2.0",
26 | "babel-loader": "^7.1.2",
27 | "babel-preset-es2015": "^6.24.1",
28 | "css-loader": "^0.28.7",
29 | "eslint": "^5.15.2",
30 | "eslint-config-airbnb-base": "^12.0.0",
31 | "eslint-plugin-import": "^2.7.0",
32 | "extract-text-webpack-plugin": "^3.0.1",
33 | "jest": "^21.2.0",
34 | "klaw-sync": "^3.0.0",
35 | "regenerator-runtime": "^0.11.0",
36 | "style-loader": "^0.19.0",
37 | "webpack": "^3.5.6",
38 | "webpack-component-loader-smarty-parser": "0.0.4"
39 | },
40 | "jest": {
41 | "collectCoverage": true,
42 | "collectCoverageFrom": [
43 | "**/lib/**"
44 | ],
45 | "coverageDirectory": "./test/coverage/"
46 | },
47 | "dependencies": {
48 | "fs-extra": "^4.0.2",
49 | "hash-sum": "^1.0.2",
50 | "is-valid-path": "^0.1.1",
51 | "loader-utils": "^1.1.0",
52 | "postcss": "^6.0.13",
53 | "postcss-selector-parser": "^2.2.3"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/test/buildTemplate.test.js:
--------------------------------------------------------------------------------
1 | import fsx from 'fs-extra';
2 | import path from 'path';
3 | import klawSync from 'klaw-sync';
4 |
5 | import buildTemplate from '../lib/buildTemplate';
6 | import buildOptionNormal from './util/buildOption.normal';
7 |
8 | describe('test buildTemplate', function() {
9 |
10 | it('should built successfully', function(cb) {
11 | const normalComponents =
12 | [
13 | {
14 | "root": "",
15 | "dir": "include",
16 | "base": "componentA.tpl",
17 | "ext": ".tpl",
18 | "name": "componentA"
19 | },
20 | {
21 | "root": "",
22 | "dir": "include",
23 | "base": "componentD.tpl",
24 | "ext": ".tpl",
25 | "name": "componentD"
26 | },
27 | {
28 | "root": "",
29 | "dir": "",
30 | "base": "componentB.tpl",
31 | "ext": ".tpl",
32 | "name": "componentB"
33 | },
34 | {
35 | "root": "/",
36 | "dir": "",
37 | "base": "pageC.tpl",
38 | "ext": ".tpl",
39 | "name": "pageC"
40 | }
41 | ];
42 | fsx.removeSync(path.join(__dirname, './assetsCoreTest'));
43 | buildTemplate(normalComponents, buildOptionNormal);
44 |
45 | setTimeout(()=>{
46 | const dirs = klawSync(path.join(__dirname, './assetsCoreTest'), {nodir: true});
47 | expect(dirs).toHaveLength(normalComponents.length);
48 | fsx.removeSync(path.join(__dirname, './assetsCoreTest'));
49 | cb();
50 | }, 0)
51 |
52 | });
53 |
54 | // TODO: should improve the test for async
55 | // it('should throw error when component is not exist', function() {
56 | // const inexistentComponents =
57 | // [
58 | // {
59 | // "root": "",
60 | // "dir": "include",
61 | // "base": "noA.tpl",
62 | // "ext": ".tpl",
63 | // "name": "noA"
64 | // },
65 | // {
66 | // "root": "/",
67 | // "dir": "",
68 | // "base": "noB.tpl",
69 | // "ext": ".tpl",
70 | // "name": "noB"
71 | // }
72 | // ];
73 | //
74 | // const errorPath = path.resolve(buildOptionNormal.srcPath, inexistentComponents[0].dir, inexistentComponents[0].name, `${inexistentComponents[0].name}${buildOptionNormal.ext}`);
75 | //
76 | // setTimeout(()=>{
77 | // const dirs = klawSync(path.join(__dirname, './assetsCoreTest'), {nodir: true});
78 | // expect(dirs).toHaveLength(normalComponents.length);
79 | // fsx.removeSync(path.join(__dirname, './assetsCoreTest'));
80 | // cb();
81 | // }, 0)
82 | //
83 | // expect(()=>{
84 | // buildTemplate(inexistentComponents, buildOptionNormal);
85 | // }).toThrowError(`[webpack-component-loader]: something wrong with building Template: ${errorPath} is non existence`);
86 | // })
87 | //
88 | // it('should throw error when on-array component was passed in', function() {
89 | // const noArray = 'NOT A ARRAY';
90 | // expect(()=>{
91 | // buildTemplate(noArray, buildOptionNormal);
92 | // }).toThrowError('[webpack-component-loader]: something wrong with building Template: components is not an array')
93 | // })
94 | //
95 | // it('should throw error when was passed in a broken component', function() {
96 | // const brokenComponents = [
97 | // 1,2,
98 | // {
99 | // "dir": "include",
100 | // "base": "noA.tpl",
101 | // "ext": ".tpl",
102 | // "name": "noA"
103 | // },
104 | // {
105 | // "root": "/",
106 | // "dir": "",
107 | // "base": "noB.tpl",
108 | // "name": "noB"
109 | // },
110 | // {}
111 | // ];
112 | // expect(()=>{
113 | // buildTemplate(brokenComponents, buildOptionNormal);
114 | // }).toThrow();
115 | // })
116 |
117 | })
118 |
119 |
120 |
--------------------------------------------------------------------------------
/test/createOutput.test.js:
--------------------------------------------------------------------------------
1 | import createOutput from '../lib/createOutput';
2 | import normalBuildOption from './util/buildOption.normal';
3 | import normalComponents from './util/components.normal';
4 |
5 |
6 | describe('test createOutput', function(){
7 | it('should pass', function(){
8 | const res = createOutput(normalComponents, normalBuildOption);
9 | expect(res).toMatch(/require\('.*'\)/);
10 | });
11 |
12 | it('should throw error when pass in a bad Array', function(){
13 | expect(()=>createOutput('NOT AN ARRAY', normalBuildOption)).toThrow('[webpack-component-loader]: the component is not an array');
14 | })
15 | })
--------------------------------------------------------------------------------
/test/findScopedSelectors.test.js:
--------------------------------------------------------------------------------
1 | import findScopedSelectors from '../lib/findScopedSelectors';
2 | import buildOption from './util/buildOption.normal';
3 |
4 | describe('test genScopeID', function(){
5 | it('should pass with normal input', function () {
6 | let components = [
7 | {
8 | "root": "/",
9 | "dir": "",
10 | "base": "scopeCompo.tpl",
11 | "ext": ".tpl",
12 | "name": "scopeCompo"
13 | }
14 | ];
15 | findScopedSelectors(components, buildOption);
16 | // TODD: should improve
17 | expect(components.every(component=>{
18 | return component.hasOwnProperty('scopedSelectors') && component.scopedSelectors !== undefined;
19 | })).toBeTruthy();
20 | })
21 | })
--------------------------------------------------------------------------------
/test/fixture/componentB/componentB.css:
--------------------------------------------------------------------------------
1 | .componentB {
2 | /* I came from B*/
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixture/componentB/componentB.js:
--------------------------------------------------------------------------------
1 | function B () {
2 | /* I came from B*/
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixture/componentB/componentB.tpl:
--------------------------------------------------------------------------------
1 | I am component B
2 | {include file='include/componentD.tpl'}
--------------------------------------------------------------------------------
/test/fixture/include/componentA/componentA.css:
--------------------------------------------------------------------------------
1 | /* scoped */
2 | .componentA {
3 | /* I come from A*/
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixture/include/componentA/componentA.js:
--------------------------------------------------------------------------------
1 | function A () {
2 | /* I came from A*/
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixture/include/componentA/componentA.tpl:
--------------------------------------------------------------------------------
1 | I am component A
2 | {include file='include/componentD.tpl'}
--------------------------------------------------------------------------------
/test/fixture/include/componentD/componentD.css:
--------------------------------------------------------------------------------
1 | .componentD .sss. ddffff{
2 | /* I come from D*/
3 | }
--------------------------------------------------------------------------------
/test/fixture/include/componentD/componentD.js:
--------------------------------------------------------------------------------
1 | function D () {
2 | /* I came from D*/
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixture/include/componentD/componentD.tpl:
--------------------------------------------------------------------------------
1 | I am component D
--------------------------------------------------------------------------------
/test/fixture/pageC.entry.js:
--------------------------------------------------------------------------------
1 | import './pageC/pageC.tpl';
--------------------------------------------------------------------------------
/test/fixture/pageC/pageC.css:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | /***************************************************
4 | * 見出し
5 | ***************************************************/
6 | h2.sectionHeadRtop {
7 | border-top: none;
8 | border-bottom: none;
9 | color: #333;
10 | font-size: 13px;
11 | padding-top: 11px;
12 | padding-bottom: 10px;
13 | background: #e4f1d7;
14 | text-shadow: none;
15 | }
16 |
17 | h2.sectionHeadRtop span {
18 | display: block;
19 | border-left: 3px solid #61aa12;
20 | padding-left: 5px;
21 | margin-left: 5px;
22 | border-radius: 1px;
23 | }
24 |
25 | /***************************************************
26 | * 都道府県選択
27 | ***************************************************/
28 | .todofuken {
29 | border-bottom: 1px solid #eee;
30 | }
31 |
32 | .todofuken .area {
33 | display: block;
34 | border-top: 1px solid #eee;
35 | }
36 |
37 | .todofuken .area .areaName {
38 | display: block;
39 | position: relative;
40 | background: #fff;
41 | padding: 15px 10px;
42 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
43 | }
44 |
45 | .todofuken .area .areaName h3 {
46 | font-size: 15px;
47 | font-weight: bold;
48 | color: #333;
49 | }
50 |
51 | .todofuken .area .areaName {
52 | border-bottom: 3px solid #eee;
53 | background: #f2f2f2;
54 | }
55 |
56 | .todofuken .area .pref li:first-child .prefecture {
57 | border-top: none;
58 | }
59 |
60 | .todofuken .area .pref {
61 | height: auto;
62 | overflow: hidden;
63 | background: #fff;
64 | padding-left: 20px;
65 | }
66 |
67 | .todofuken .area .pref .prefecture {
68 | display: block;
69 | padding: 17px 17px 17px 10px;
70 | font-size: 15px;
71 | color: #333;
72 | border-top: 1px solid #eee;
73 | position: relative;
74 | }
75 |
76 | .todofuken .area .pref .prefecture.ui-hover {
77 | background: #f9f9f9;
78 | }
79 |
80 | .todofuken .area .pref .prefecture:after {
81 | display: block;
82 | content: " ";
83 | position: absolute;
84 | width: 9px;
85 | height: 17px;
86 | background-image: url(/img/chintai/top/kensentaku/sprite/icon.png?id=CACHE_REVISION);
87 | background-repeat: no-repeat;
88 | background-size: 20px 57px;
89 | -webkit-background-size: 20px 57px;
90 | background-position: 0 0;
91 | top: 50%;
92 | margin-top: -8px;
93 | right: 15px;
94 | }
--------------------------------------------------------------------------------
/test/fixture/pageC/pageC.js:
--------------------------------------------------------------------------------
1 | function C () {
2 | /* I came from C*/
3 | let a = 1;
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixture/pageC/pageC.tpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {include file='include/componentA.tpl'}
7 |
8 | {include file='componentB.tpl'}
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/fixture/pageE.entry.js:
--------------------------------------------------------------------------------
1 | import './pageE/pageE.tpl';
--------------------------------------------------------------------------------
/test/fixture/pageE/pageE.css:
--------------------------------------------------------------------------------
1 | .pageE {
2 | /* I come from E*/
3 | }
4 |
5 | .common {
6 | /* common */
7 | }
--------------------------------------------------------------------------------
/test/fixture/pageE/pageE.js:
--------------------------------------------------------------------------------
1 | function E () {
2 | /* I came from E*/
3 | let a = 1;
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixture/pageE/pageE.tpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {include file='include/componentD.tpl'}
7 |
8 | {include file='componentB.tpl'}
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/fixture/scopeCompo/scopeCompo.css:
--------------------------------------------------------------------------------
1 | .scopedClass {
2 | background: moccasin;
3 | }
4 |
5 | .scopedClassS {
6 | background: moccasin;
7 | }
--------------------------------------------------------------------------------
/test/fixture/scopeCompo/scopeCompo.tpl:
--------------------------------------------------------------------------------
1 |
2 |
I am scopeCompo
3 |
I am scopeCompo
4 |
without className
5 |
element with global namespace
6 |
without className
7 |
--------------------------------------------------------------------------------
/test/genScopeID.test.js:
--------------------------------------------------------------------------------
1 | import genScopeID from '../lib/genScopeID';
2 | import components from './util/components.normal';
3 |
4 | describe('test genScopeID', function(){
5 | it('should pass with normal input', function () {
6 | genScopeID(components);
7 | expect(components.every(component=>{
8 | return component.hasOwnProperty('scopeID') && component.scopeID !== undefined;
9 | })).toBeTruthy();
10 | })
11 | })
--------------------------------------------------------------------------------
/test/parsePage.test.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import fs from 'fs';
3 |
4 | import buildOptionNormal from './util/buildOption.normal';
5 | import parsePage from '../lib/parsePage';
6 |
7 | describe('test parsePage', function() {
8 | it('should build successfully', function() {
9 | const source = fs.readFileSync(path.join(__dirname, './fixture/pageC/pageC.tpl'), 'utf8');
10 | const components = parsePage(source, buildOptionNormal);
11 | expect(components).toHaveLength(4);
12 | });
13 |
14 | it('should throw error when something wrong with extractor', function() {
15 | const source = fs.readFileSync(path.join(__dirname, './fixture/pageC/pageC.tpl'), 'utf8');
16 | const badOptions = Object.create(buildOptionNormal);
17 | badOptions.extractor = ()=>{ throw 'the extractor is down!'};
18 | expect(()=>{
19 | parsePage(source, badOptions);
20 | }).toThrowError('[webpack-component-loader]: something wrong with the extractor: the extractor is down!');
21 | });
22 |
23 | it('should throw error when something wrong with extractor', function() {
24 | const source = fs.readFileSync(path.join(__dirname, './fixture/pageC/pageC.tpl'), 'utf8');
25 | const badOptions = Object.create(buildOptionNormal);
26 | badOptions.extractor = ()=>{ return null};
27 | expect(()=>{
28 | parsePage(source, badOptions)
29 | }).toThrowError('[webpack-component-loader]: the result of extractor is not an array');
30 | });
31 | })
32 |
--------------------------------------------------------------------------------
/test/setScope.test.js:
--------------------------------------------------------------------------------
1 | import {addScopeAttr} from 'webpack-component-loader-smarty-parser';
2 | // import normalComponent from "./fixture/scopeCompo/scopeCompo.tpl";
3 |
4 | describe('test setScope', function(){
5 |
6 | it('should pass with normal', function(){
7 | expect.assertions(1);
8 | const normalTemplate =
9 | ``+
10 | `
I am scopeCompo
`+
11 | `
I am scopeCompo
`+
12 | `
without className
`+
13 | `
element with global namespace
`+
14 | `
without className
`+
15 | `
`;
16 |
17 | const component = {
18 | scopedSelectors: ['scopedClass', 'scopedClassS'],
19 | scopeID: 'dr2343d'
20 | };
21 |
22 | return addScopeAttr(normalTemplate, component).then((res) => {
23 | expect(res).toMatch(
24 | ``+
25 | `
I am scopeCompo
`+
26 | `
I am scopeCompo
`+
27 | `
without className
`+
28 | `
element with global namespace
`+
29 | `
without className
`+
30 | `
`);
31 | });
32 |
33 | })
34 | })
--------------------------------------------------------------------------------
/test/testInWebpack.test.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import fsx from 'fs-extra';
3 | import path from 'path';
4 |
5 | import config from './util/webpack.config.normal';
6 |
7 | describe('test in webpack', function () {
8 | it('should be built successfully in webpack', function(done) {
9 | function cb (err, stats) {
10 | if (err) {
11 | console.error(err.stack || err);
12 | if (err.details) {
13 | console.error(err.details);
14 | }
15 | }
16 | expect(err).toBeNull();
17 | const builtTemplates = fsx.pathExistsSync(path.resolve(__dirname, './assets/templates'));
18 | const builtJS = fsx.pathExistsSync(path.resolve(__dirname, './assets/js'));
19 | const builtCSS = fsx.pathExistsSync(path.resolve(__dirname, './assets/css'));
20 | expect(builtTemplates).toBeTruthy();
21 | expect(builtJS).toBeTruthy();
22 | expect(builtCSS).toBeTruthy();
23 | fsx.removeSync(path.resolve(__dirname, './assets'));
24 | done();
25 | };
26 | fsx.removeSync(path.resolve(__dirname, './assets'));
27 | webpack (config, cb);
28 | });
29 |
30 |
31 | it('unvalidate path should not be passed', function(done){
32 | function cb (err, stats) {
33 | if (err) {
34 | console.error(err.stack || err);
35 | if (err.details) {
36 | console.error(err.details);
37 | }
38 | }
39 | const builtTemplates = fsx.pathExistsSync(path.resolve(__dirname, './assets/templates'));
40 | // const builtJS = fsx.pathExistsSync(path.resolve(__dirname, './assets/js'));
41 | const builtCSS = fsx.pathExistsSync(path.resolve(__dirname, './assets/css'));
42 | expect(builtTemplates).toBeFalsy();
43 | // expect(builtJS).toBeFalsy();
44 | expect(builtCSS).toBeFalsy();
45 | fsx.removeSync(path.resolve(__dirname, './assets'));
46 | done();
47 | };
48 | const badConfig = Object.create(config);
49 | badConfig.module.rules[0].use[0].options.srcPath = '/!!!!';
50 | fsx.removeSync(path.resolve(__dirname, './assets'));
51 | webpack(badConfig, cb );
52 | });
53 |
54 |
55 | it('unvalidate extractor should not be passed', function(done){
56 | function cb (err, stats) {
57 | if (err) {
58 | console.error(err.stack || err);
59 | if (err.details) {
60 | console.error(err.details);
61 | }
62 | }
63 | const builtTemplates = fsx.pathExistsSync(path.resolve(__dirname, './assets/templates'));
64 | // const builtJS = fsx.pathExistsSync(path.resolve(__dirname, './assets/js'));
65 | const builtCSS = fsx.pathExistsSync(path.resolve(__dirname, './assets/css'));
66 | expect(builtTemplates).toBeFalsy();
67 | // expect(builtJS).toBeFalsy();
68 | expect(builtCSS).toBeFalsy();
69 | fsx.removeSync(path.resolve(__dirname, './assets'));
70 | done();
71 | };
72 | const badConfig = Object.create(config);
73 | badConfig.module.rules[0].use[0].options.extractor = {};
74 | fsx.removeSync(path.resolve(__dirname, './assets'));
75 | webpack(badConfig, cb );
76 | });
77 | })
--------------------------------------------------------------------------------
/test/util/buildOption.normal.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const {extractor, addScopeAttr} = require('webpack-component-loader-smarty-parser');
3 |
4 | export default {
5 | extractor,
6 | addScopeAttr,
7 | ext: '.tpl',
8 | isCodeSplit: false,
9 | srcPath: path.join(__dirname, '../fixture'),
10 | builtJSPath: path.join(__dirname, '../assetsCoreTest/js'),
11 | builtCSSPath: path.join(__dirname, '../assetsCoreTest/css'),
12 | builtTemplatePath: path.join(__dirname, '../assetsCoreTest/templates'),
13 | currentPagePath: path.join(__dirname, '../fixture/pageC/pageC.tpl'),
14 | };
--------------------------------------------------------------------------------
/test/util/components.normal.js:
--------------------------------------------------------------------------------
1 | export default
2 | [
3 | {
4 | "root": "",
5 | "dir": "include",
6 | "base": "componentA.tpl",
7 | "ext": ".tpl",
8 | "name": "componentA"
9 | },
10 | {
11 | "root": "",
12 | "dir": "include",
13 | "base": "componentD.tpl",
14 | "ext": ".tpl",
15 | "name": "componentD"
16 | },
17 | {
18 | "root": "",
19 | "dir": "",
20 | "base": "componentB.tpl",
21 | "ext": ".tpl",
22 | "name": "componentB"
23 | },
24 | {
25 | "root": "/",
26 | "dir": "",
27 | "base": "pageC.tpl",
28 | "ext": ".tpl",
29 | "name": "pageC"
30 | }
31 | ];
--------------------------------------------------------------------------------
/test/util/webpack.config.normal.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
3 | const webpack = require('webpack');
4 |
5 | const {extractor, addScopeAttr, injector} = require('webpack-component-loader-smarty-parser');
6 |
7 | module.exports = {
8 | entry: {
9 | 'pageC': path.resolve(__dirname, '../fixture/pageC.entry.js'),
10 | 'pageE': path.resolve(__dirname, '../fixture/pageE.entry.js'),
11 | },
12 | output: {
13 | path: path.resolve(__dirname, '../assets/'),
14 | publicPath: '/assets/',
15 | filename : 'js/[name].js',
16 | chunkFilename: 'js/[name].chunk.js',
17 | },
18 | resolveLoader: {
19 | alias: {
20 | 'webpack-component-loader': path.resolve(__dirname, '../../index.js'),
21 | },
22 | },
23 | module: {
24 | rules: [
25 | {
26 | test : /\.tpl?$/,
27 | exclude: /(node_modules)/,
28 | use: [
29 | {
30 | loader: 'webpack-component-loader',
31 | options: {
32 | isCodeSplit: false,
33 | extractor,
34 | injector,
35 | addScopeAttr,
36 | ext: '.tpl',
37 | srcPath : path.resolve(__dirname, '../fixture'),
38 | builtTemplatePath : path.resolve(__dirname, '../assets/templates'),
39 | },
40 | },
41 | ],
42 | },
43 | {
44 | test: /\.css$/,
45 | exclude: /(node_modules)/,
46 | enforce: 'post',
47 | use: ExtractTextPlugin.extract({
48 | fallback: 'style-loader',
49 | use: 'css-loader'
50 | })
51 | }
52 | ],
53 | },
54 | plugins: [
55 | new ExtractTextPlugin({
56 | filename: 'css/[name].css',
57 | // allChunks: true
58 | }),
59 | new webpack.optimize.CommonsChunkPlugin({
60 | name: 'commons',
61 | filename: 'js/commons.js',
62 | })
63 | ]
64 | }
--------------------------------------------------------------------------------