├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── example ├── component-server │ ├── app.js │ ├── bin │ │ └── www │ ├── public │ │ └── stylesheets │ │ │ └── style.css │ ├── routes │ │ ├── index.js │ │ └── users.js │ └── views │ │ ├── clock.ejs │ │ ├── error.ejs │ │ └── navigator.ejs ├── index-server │ ├── app.js │ ├── bin │ │ └── www │ ├── public │ │ └── stylesheets │ │ │ └── style.css │ ├── routes │ │ ├── index.js │ │ └── users.js │ └── views │ │ ├── error.ejs │ │ └── index.ejs ├── jadeWithPostHTML.js ├── list-server │ ├── app.js │ ├── bin │ │ └── www │ ├── public │ │ └── stylesheets │ │ │ └── style.css │ ├── routes │ │ ├── index.js │ │ └── users.js │ └── views │ │ ├── error.ejs │ │ └── index.ejs └── package.json ├── gulp-test ├── gulpfile.js ├── html │ ├── hello-world.html │ └── index.html └── package.json ├── package.json ├── src ├── LinkImport.js └── index.js └── test ├── LinkImport-spec.js ├── fixtures ├── Caculator.html ├── hello-world.html ├── index.html └── result.txt └── index-spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | .nyc_output 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | dest 30 | package-lock.json -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "12" 5 | - "10" 6 | - "8" 7 | after_success: 8 | - npm run coveralls 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Zhi Cun 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # posthtml-web-component 2 | 3 | [![npm version](https://badge.fury.io/js/posthtml-web-component.svg)](https://badge.fury.io/js/posthtml-web-component) 4 | [![Build Status](https://travis-ci.org/posthtml/posthtml-web-component.svg?branch=master)](https://travis-ci.org/posthtml/posthtml-web-component.svg?branch=master) 5 | [![Coverage Status](https://coveralls.io/repos/posthtml/posthtml-web-component/badge.svg)](https://coveralls.io/github/posthtml/posthtml-web-component) 6 | 7 | [PostHTML](https://github.com/posthtml/posthtml) plugin for Server Side Web Component Render. 8 | 9 | ## Feature 10 | 11 | - Base Web Component Server Side Rending 12 | - Component as a Sevice 13 | 14 | ## Advantage 15 | 16 | ## Explanation 17 | 18 | ### Web Component 19 | 20 | Must mention that `Web Components` supported by `posthtml-web-component` don't completely follow the [Web Components](http://www.w3.org/TR/components-intro/) draft. 21 | 22 | A typical posthtml web component looks as following: 23 | 24 | ```html 25 | 26 | 45 | 52 | 68 | ``` 69 | 70 | This is a runnable component itself. Consider there is a `index.html`: 71 | 72 | ```html 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | ``` 87 | 88 | After `posthtml-web-component`'s transforming: 89 | 90 | ```html 91 | 92 | 93 | 94 | 95 | 96 | 97 | 116 | 117 | 118 |
119 |
HH
120 |
MM
121 |
SS
122 |
123 |
124 |
HH
125 |
MM
126 |
SS
127 |
128 | 144 | 145 | 146 | ``` 147 | 148 | Work fine! 149 | 150 | ### LinkImport 151 | 152 | We have two types of `LinkImport`, local and remote. 153 | 154 | ```html 155 | 156 | 157 | 158 | 159 | 160 | ``` 161 | 162 | The difference of these two types is that remote `LinkImport` could call a remote service, this is to say remote `LinkImport` could be dynamic. 163 | -------------------------------------------------------------------------------- /example/component-server/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var routes = require('./routes/index'); 9 | var users = require('./routes/users'); 10 | 11 | var app = express(); 12 | 13 | // view engine setup 14 | app.set('views', path.join(__dirname, 'views')); 15 | // app.engine('ejs', require('../jadeWithPostHTML')); 16 | app.set('view engine', 'ejs'); 17 | 18 | // uncomment after placing your favicon in /public 19 | //app.use(favicon(__dirname + '/public/favicon.ico')); 20 | app.use(logger('dev')); 21 | app.use(bodyParser.json()); 22 | app.use(bodyParser.urlencoded({ extended: false })); 23 | app.use(cookieParser()); 24 | app.use(express.static(path.join(__dirname, 'public'))); 25 | 26 | app.use('/', routes); 27 | app.use('/users', users); 28 | 29 | // catch 404 and forward to error handler 30 | app.use(function(req, res, next) { 31 | var err = new Error('Not Found'); 32 | err.status = 404; 33 | next(err); 34 | }); 35 | 36 | // error handlers 37 | 38 | // development error handler 39 | // will print stacktrace 40 | if (app.get('env') === 'development') { 41 | app.use(function(err, req, res, next) { 42 | res.status(err.status || 500); 43 | res.render('error', { 44 | message: err.message, 45 | error: err 46 | }); 47 | }); 48 | } 49 | 50 | // production error handler 51 | // no stacktraces leaked to user 52 | app.use(function(err, req, res, next) { 53 | res.status(err.status || 500); 54 | res.render('error', { 55 | message: err.message, 56 | error: {} 57 | }); 58 | }); 59 | 60 | 61 | module.exports = app; 62 | -------------------------------------------------------------------------------- /example/component-server/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var debug = require('debug')('index-server'); 3 | var app = require('../app'); 4 | 5 | app.set('port', process.env.PORT || 3000); 6 | 7 | var server = app.listen(app.get('port'), function() { 8 | debug('Express server listening on port ' + server.address().port); 9 | }); 10 | -------------------------------------------------------------------------------- /example/component-server/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /example/component-server/routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET navigator component. */ 5 | router.get('/navigator', function(req, res) { 6 | res.render('navigator', { 7 | current: req.query.current || 'home' 8 | }); 9 | }); 10 | 11 | /* GET clock component. */ 12 | router.get('/clock', function(req, res) { 13 | res.render('clock'); 14 | }); 15 | 16 | module.exports = router; 17 | -------------------------------------------------------------------------------- /example/component-server/routes/users.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET users listing. */ 5 | router.get('/', function(req, res) { 6 | res.send('respond with a resource'); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /example/component-server/views/clock.ejs: -------------------------------------------------------------------------------- 1 | 20 |
21 |
HH
22 |
MM
23 |
SS
24 |
25 | 41 | -------------------------------------------------------------------------------- /example/component-server/views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /example/component-server/views/navigator.ejs: -------------------------------------------------------------------------------- 1 | 10 | 14 | 17 | -------------------------------------------------------------------------------- /example/index-server/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var routes = require('./routes/index'); 9 | var users = require('./routes/users'); 10 | 11 | var app = express(); 12 | 13 | // view engine setup 14 | app.set('views', path.join(__dirname, 'views')); 15 | app.engine('ejs', require('../jadeWithPostHTML')); 16 | app.set('view engine', 'ejs'); 17 | 18 | // uncomment after placing your favicon in /public 19 | //app.use(favicon(__dirname + '/public/favicon.ico')); 20 | app.use(logger('dev')); 21 | app.use(bodyParser.json()); 22 | app.use(bodyParser.urlencoded({ extended: false })); 23 | app.use(cookieParser()); 24 | app.use(express.static(path.join(__dirname, 'public'))); 25 | 26 | app.use('/', routes); 27 | app.use('/users', users); 28 | 29 | // catch 404 and forward to error handler 30 | app.use(function(req, res, next) { 31 | var err = new Error('Not Found'); 32 | err.status = 404; 33 | next(err); 34 | }); 35 | 36 | // error handlers 37 | 38 | // development error handler 39 | // will print stacktrace 40 | if (app.get('env') === 'development') { 41 | app.use(function(err, req, res, next) { 42 | res.status(err.status || 500); 43 | res.render('error', { 44 | message: err.message, 45 | error: err 46 | }); 47 | }); 48 | } 49 | 50 | // production error handler 51 | // no stacktraces leaked to user 52 | app.use(function(err, req, res, next) { 53 | res.status(err.status || 500); 54 | res.render('error', { 55 | message: err.message, 56 | error: {} 57 | }); 58 | }); 59 | 60 | 61 | module.exports = app; 62 | -------------------------------------------------------------------------------- /example/index-server/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var debug = require('debug')('index-server'); 3 | var app = require('../app'); 4 | 5 | app.set('port', process.env.PORT || 5000); 6 | 7 | var server = app.listen(app.get('port'), function() { 8 | debug('Express server listening on port ' + server.address().port); 9 | }); 10 | -------------------------------------------------------------------------------- /example/index-server/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /example/index-server/routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res) { 6 | res.render('index', { title: 'Index Server' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /example/index-server/routes/users.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET users listing. */ 5 | router.get('/', function(req, res) { 6 | res.send('respond with a resource'); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /example/index-server/views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /example/index-server/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 | 9 | 10 |

posthtml-web-component demo

11 | 12 |

Welcome to <%= title %>

13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/jadeWithPostHTML.js: -------------------------------------------------------------------------------- 1 | var posthtml = require('posthtml') 2 | module.exports = function (path, options, callback) { 3 | 4 | var html = require('ejs').renderFile(path, options, function(err, html) { 5 | if (err) { 6 | return callback(err) 7 | } 8 | posthtml([ 9 | require('../src/index')({ 10 | hostURI:path 11 | }) 12 | ]) 13 | .process(html) 14 | .then(function (result) { 15 | if (typeof callback === 'function') { 16 | var res; 17 | try { 18 | res = result.html; 19 | } catch (ex) { 20 | return callback(ex); 21 | } 22 | return callback(null, res); 23 | } 24 | }); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /example/list-server/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var routes = require('./routes/index'); 9 | var users = require('./routes/users'); 10 | 11 | var app = express(); 12 | 13 | // view engine setup 14 | app.set('views', path.join(__dirname, 'views')); 15 | app.engine('ejs', require('../jadeWithPostHTML')); 16 | app.set('view engine', 'ejs'); 17 | 18 | // uncomment after placing your favicon in /public 19 | //app.use(favicon(__dirname + '/public/favicon.ico')); 20 | app.use(logger('dev')); 21 | app.use(bodyParser.json()); 22 | app.use(bodyParser.urlencoded({ extended: false })); 23 | app.use(cookieParser()); 24 | app.use(express.static(path.join(__dirname, 'public'))); 25 | 26 | app.use('/', routes); 27 | app.use('/users', users); 28 | 29 | // catch 404 and forward to error handler 30 | app.use(function(req, res, next) { 31 | var err = new Error('Not Found'); 32 | err.status = 404; 33 | next(err); 34 | }); 35 | 36 | // error handlers 37 | 38 | // development error handler 39 | // will print stacktrace 40 | if (app.get('env') === 'development') { 41 | app.use(function(err, req, res, next) { 42 | res.status(err.status || 500); 43 | res.render('error', { 44 | message: err.message, 45 | error: err 46 | }); 47 | }); 48 | } 49 | 50 | // production error handler 51 | // no stacktraces leaked to user 52 | app.use(function(err, req, res, next) { 53 | res.status(err.status || 500); 54 | res.render('error', { 55 | message: err.message, 56 | error: {} 57 | }); 58 | }); 59 | 60 | 61 | module.exports = app; 62 | -------------------------------------------------------------------------------- /example/list-server/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var debug = require('debug')('index-server'); 3 | var app = require('../app'); 4 | 5 | app.set('port', process.env.PORT || 4000); 6 | 7 | var server = app.listen(app.get('port'), function() { 8 | debug('Express server listening on port ' + server.address().port); 9 | }); 10 | -------------------------------------------------------------------------------- /example/list-server/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /example/list-server/routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res) { 6 | res.render('index', { title: 'List Server' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /example/list-server/routes/users.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET users listing. */ 5 | router.get('/', function(req, res) { 6 | res.send('respond with a resource'); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /example/list-server/views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /example/list-server/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 | 9 |

posthtml-web-component demo

10 | 11 |

Welcome to <%= title %>

12 | 13 | 14 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "compnent-server", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "express": "~4.9.0", 10 | "body-parser": "~1.8.1", 11 | "cookie-parser": "~1.3.3", 12 | "morgan": "~1.3.0", 13 | "serve-favicon": "~2.1.3", 14 | "debug": "~2.0.0", 15 | "ejs": "~0.8.5" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /gulp-test/gulpfile.js: -------------------------------------------------------------------------------- 1 | var posthtml = require('gulp-posthtml'); 2 | var gulp = require('gulp'); 3 | 4 | gulp.task('posthtml', function (done) { 5 | var plugins = [ 6 | require('../')() 7 | ]; 8 | return gulp.src('./html/index.html') 9 | .pipe(posthtml(plugins)) 10 | .pipe(gulp.dest('./dest')); 11 | }); 12 | -------------------------------------------------------------------------------- /gulp-test/html/hello-world.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 60 | -------------------------------------------------------------------------------- /gulp-test/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | hello-world 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /gulp-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "gulpfile.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "gulp": "^3.9.0", 13 | "gulp-posthtml": "^1.5.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "posthtml-web-component", 3 | "version": "0.1.0", 4 | "description": "Server Side Web Component Render.", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "nyc mocha", 8 | "coverage": "nyc report --reporter=text-lcov | coveralls" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/island205/posthtml-web-component.git" 13 | }, 14 | "keywords": [ 15 | "web", 16 | "component", 17 | "posthtml", 18 | "posthtml-plugin", 19 | "html", 20 | "webcomponent", 21 | "node" 22 | ], 23 | "author": "island205@gmail.com", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/island205/posthtml-web-component/issues" 27 | }, 28 | "homepage": "https://github.com/island205/posthtml-web-component#readme", 29 | "dependencies": { 30 | "debug": "^2.2.0", 31 | "posthtml": "^0.11.6", 32 | "request": "^2.67.0" 33 | }, 34 | "devDependencies": { 35 | "chai": "^3.4.1", 36 | "coveralls": "^2.13.3", 37 | "mocha": "^6.2.0", 38 | "nyc": "^14.1.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/LinkImport.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var posthtml = require('posthtml') 3 | var request = require('request') 4 | var debug = require('debug')('posthtml-web-component:LinkImport') 5 | var fs = require('fs') 6 | var url = require('url') 7 | 8 | function LinkImport(customElementTagName, uri, originURI) { 9 | this.customElementTagName = customElementTagName 10 | this.uri = uri 11 | this.originURI = originURI 12 | } 13 | 14 | LinkImport.prototype.load = function() { 15 | debug('load LinkImport', this.customElementTagName) 16 | return new Promise(function (resolve, reject) { 17 | if (/^http:\/\//.test(this.uri)) { 18 | request(this.uri, function(error, response, body) { 19 | debug('LinkImport', this.customElementTagName, 'loaded') 20 | if (error) { 21 | reject(error) 22 | } else { 23 | this.source = body 24 | resolve('loaded') 25 | } 26 | }.bind(this)) 27 | } else { 28 | fs.readFile(this.uri, 'utf-8', function (error, data) { 29 | debug('LinkImport', this.customElementTagName, 'loaded') 30 | if (error) { 31 | reject(error) 32 | } else { 33 | this.source = data 34 | resolve('loaded') 35 | } 36 | }.bind(this)) 37 | } 38 | }.bind(this)) 39 | } 40 | 41 | LinkImport.prototype.prepare = function () { 42 | var parts = this.parts = { 43 | styles: [], 44 | scripts: [], 45 | html: null 46 | } 47 | posthtml().use(function(tree) { 48 | tree.walk(function (node) { 49 | if (node.tag === 'script') { 50 | parts.scripts.push(node) 51 | // remove script node from template 52 | return undefined 53 | } else if (node.tag === 'style' || (node.tag === 'link' && node.attrs.rel ==='stylesheet')) { 54 | parts.styles.push(node) 55 | // remove style node from template 56 | return undefined 57 | } else if (node.tag === 'template') { 58 | // if LinkImport has an template tag, use it's innerHTML as custom element's html 59 | parts.html = node.content 60 | } 61 | return node 62 | }) 63 | // if no template tag, use itself as custom element's html 64 | this.parts.html || (this.parts.html = tree) 65 | 66 | // https://github.com/posthtml/posthtml-render/pull/2 67 | this.parts.html = { 68 | tag: 'div', 69 | attrs: { 70 | 'class': this.getCustomElementTagName() 71 | }, 72 | content: this.parts.html 73 | } 74 | 75 | }.bind(this)).process(this.source, {sync: true}) 76 | } 77 | LinkImport.parse = function (node, options) { 78 | if (!(options && options.hostURI)) { 79 | throw new Error('The hostURI is needed in options') 80 | } 81 | var customElementTagName, uri, originURI 82 | originURI = node.attrs.href 83 | var pathname 84 | if (/^(http|https):\/\//.test(originURI)) { 85 | uri = originURI 86 | pathname = url.parse(uri).pathname 87 | } else { 88 | uri = pathname = path.resolve(path.dirname(options.hostURI), originURI) 89 | } 90 | customElementTagName = path.parse(pathname).name 91 | return new LinkImport(customElementTagName, uri, originURI) 92 | } 93 | 94 | 95 | LinkImport.prototype.loaded = function () { 96 | return !!this.source 97 | } 98 | 99 | LinkImport.prototype.getCustomElementTagName = function () { 100 | return this.customElementTagName 101 | } 102 | 103 | LinkImport.prototype.getStyles = function () { 104 | return this.parts.styles 105 | } 106 | 107 | LinkImport.prototype.getScripts = function () { 108 | return this.parts.scripts 109 | } 110 | 111 | LinkImport.prototype.getHTML = function () { 112 | return this.parts.html 113 | } 114 | 115 | module.exports = LinkImport 116 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | var LinkImport = require('./LinkImport') 2 | var debug = require('debug')('posthtml-web-component:index') 3 | 4 | module.exports = function (options) { 5 | options = options || {} 6 | return function webComponent(tree, cb) { 7 | var LinkImports = [] 8 | tree.walk(function (node) { 9 | if (node.tag === 'link' && 10 | node.attrs.rel === 'import' && 11 | node.attrs.href) { 12 | LinkImports.push(LinkImport.parse(node, { 13 | hostURI: options.hostURI || tree.options.path || '' 14 | })) 15 | // remove LinkImport from origin html 16 | return undefined 17 | } 18 | return node 19 | }) 20 | debug('parse all LinkImports', LinkImports) 21 | Promise.all(LinkImports.map(function (linkImport) { 22 | return linkImport.load() 23 | })).then(onAllLoaded, onAllLoaded) 24 | 25 | function onAllLoaded() { 26 | debug('onAllLoaded') 27 | var resources = { 28 | styles: [], 29 | scripts: [] 30 | } 31 | LinkImports.filter(function (linkImport) { 32 | return linkImport.loaded() 33 | }).reduce(function (resources, currentLinkImport) { 34 | currentLinkImport.prepare() 35 | resources.styles.push.apply(resources.styles, currentLinkImport.getStyles()) 36 | resources.scripts.push.apply(resources.scripts, currentLinkImport.getScripts()) 37 | tree.match({tag: currentLinkImport.getCustomElementTagName()}, function (node) { 38 | return currentLinkImport.getHTML() 39 | }) 40 | return resources 41 | }, resources) 42 | debug('prepare all resources', resources, 'done') 43 | tree.walk(function(node) { 44 | if (node && node.tag === 'head') { 45 | node.content.push.apply(node.content, resources.styles) 46 | } 47 | if (node && node.tag === 'body') { 48 | node.content.push.apply(node.content, resources.scripts) 49 | } 50 | return node 51 | }) 52 | cb(null, tree) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/LinkImport-spec.js: -------------------------------------------------------------------------------- 1 | var posthtml = require('posthtml') 2 | var fs = require('fs') 3 | var path = require('path') 4 | var expect = require('chai').expect 5 | 6 | var LinkImport = require('../src/LinkImport') 7 | 8 | function fixture(filePath) { 9 | return fs.readFileSync(path.join(__dirname, filePath), 'utf-8') 10 | } 11 | 12 | var indexHTML = fixture('fixtures/index.html') 13 | 14 | describe('LinkImport', function () { 15 | describe('.parse', function () { 16 | it('should parse LinkImport info from an relative link node', function () { 17 | var node = { 18 | tag: 'link', 19 | attrs: { 20 | rel: 'import', 21 | href: 'hello-world.html' 22 | } 23 | } 24 | var linkImport = LinkImport.parse(node, { 25 | hostURI: path.join(__dirname, './fixtures/index.html') 26 | }) 27 | 28 | expect({ 29 | customElementTagName: linkImport.getCustomElementTagName(), 30 | originURI: linkImport.originURI, 31 | uri: linkImport.uri 32 | }).to.eql({ 33 | customElementTagName: 'hello-world', 34 | originURI: 'hello-world.html', 35 | uri: path.join(__dirname, './fixtures/hello-world.html') 36 | }) 37 | 38 | }) 39 | it('should parse LinkImport info from remote link node', function () { 40 | var node = { 41 | tag: 'link', 42 | attrs: { 43 | rel: 'import', 44 | href: 'https://google.com/hello-world.html' 45 | } 46 | } 47 | var linkImport = LinkImport.parse(node, { 48 | hostURI: path.join(__dirname, './fixtures/index.html') 49 | }) 50 | 51 | expect({ 52 | customElementTagName: linkImport.getCustomElementTagName(), 53 | originURI: linkImport.originURI, 54 | uri: linkImport.uri 55 | }).to.eql({ 56 | customElementTagName: 'hello-world', 57 | originURI: 'https://google.com/hello-world.html', 58 | uri: 'https://google.com/hello-world.html' 59 | }) 60 | }) 61 | }) 62 | describe('.prototype.load', function () { 63 | it('should load file from file system', function (done) { 64 | var linkImport = new LinkImport('', path.join(__dirname, './fixtures/index.html'), '') 65 | linkImport.load() 66 | .then(function () { 67 | expect(linkImport.source).to.eql(indexHTML) 68 | }).then(done, done) 69 | }) 70 | 71 | it('should load file from remote server', function(done) { 72 | var linkImport = new LinkImport('', 'http://island205.github.io/ReactUnitTesting/Caculator/', '') 73 | linkImport.load() 74 | .then(function () { 75 | expect(linkImport.source).to.eql(fixture('fixtures/Caculator.html')) 76 | }).then(done, done) 77 | }) 78 | }) 79 | 80 | describe('.prototype.prepare', function () { 81 | 82 | it('should seperate LinkImport source into style script and html parts', function () { 83 | var linkImport = new LinkImport() 84 | linkImport.source = fixture('fixtures/hello-world.html') 85 | linkImport.prepare() 86 | expect(linkImport.getScripts().length).to.eql(1) 87 | expect(linkImport.getStyles().length).to.eql(1) 88 | expect(linkImport.getHTML()).to.not.be.undefined 89 | }) 90 | }) 91 | }) 92 | -------------------------------------------------------------------------------- /test/fixtures/Caculator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Caculator 4 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/fixtures/hello-world.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 60 | -------------------------------------------------------------------------------- /test/fixtures/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <hello-world> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/fixtures/result.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <hello-world> 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 |
20 |

Hello :)

21 |
22 | 23 | 71 | 72 | -------------------------------------------------------------------------------- /test/index-spec.js: -------------------------------------------------------------------------------- 1 | var posthtml = require('posthtml') 2 | var fs = require('fs') 3 | var path = require('path') 4 | var expect = require('chai').expect 5 | 6 | 7 | function fixture(filePath) { 8 | return fs.readFileSync(path.join(__dirname, filePath), 'utf-8') 9 | } 10 | 11 | var indexHTML = fixture('fixtures/index.html') 12 | 13 | describe('WebComponent', function () { 14 | it('should parse web component', function (done) { 15 | var webComponent = posthtml().use(require('../src/index')({ 16 | hostURI: path.join(__dirname, './fixtures/index.html') 17 | })) 18 | webComponent.process(indexHTML) 19 | .then(function (result) { 20 | expect(result.html).to.eql(fixture('fixtures/result.txt')) 21 | }).then(done, done) 22 | }) 23 | }) 24 | --------------------------------------------------------------------------------