├── .jshintrc ├── .travis.yml ├── version.js ├── .editorconfig ├── bower.json ├── .gitignore ├── csst.min.js ├── test ├── csst.js └── lib │ └── dom.js ├── example ├── index.html └── app.js ├── package.json ├── gulpfile.js ├── README.md ├── csst.js └── src └── csst.js /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "undef": false 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4.4.7 4 | script: 5 | - npm run lint 6 | - npm run test 7 | after_script: 8 | - "npm install coveralls@2.10.0 && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 9 | branches: 10 | only: 11 | - master -------------------------------------------------------------------------------- /version.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | 4 | var filename = path.join(__dirname, 'package.json'); 5 | var package = JSON.parse(fs.readFileSync(filename)); 6 | package.version = package.version.replace(/-?\d+$/, function(value) { 7 | return parseInt(value) + 1; 8 | }); 9 | 10 | fs.writeFileSync(filename, JSON.stringify(package, null, ' ')); -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | indent_style = tab 7 | indent_size = 4 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [*.{html,js,ts,css,scss,xml,json}] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.yml] 20 | indent_style = space 21 | indent_size = 2 -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csst", 3 | "version": "0.1.1", 4 | "homepage": "https://github.com/zswang/csst", 5 | "authors": [ 6 | { 7 | "name": "zswang", 8 | "url": "http://weibo.com/zswang" 9 | } 10 | ], 11 | "description": "CSS Text Transformation", 12 | "main": "csst.js", 13 | "moduleType": [ 14 | "globals" 15 | ], 16 | "keywords": [ 17 | "CSS", 18 | "Transformation", 19 | "CSST" 20 | ], 21 | "license": "MIT", 22 | "devDependencies": {}, 23 | "ignore": [ 24 | "src", 25 | "**/.*", 26 | "node_modules", 27 | "bower_components", 28 | "app/components", 29 | "test", 30 | "tests" 31 | ] 32 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # vim 6 | *.sw[a-z] 7 | vim/.netrwhist 8 | 9 | # temp 10 | .DS_Store 11 | *.db 12 | *.bak 13 | *.tmp 14 | *.cmd 15 | ~* 16 | *.idea 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directory 28 | # Deployed apps should consider commenting this line out: 29 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 30 | node_modules 31 | 32 | # Bower 33 | coverage 34 | bower_components 35 | 36 | # test 37 | assets 38 | 39 | # release 40 | release 41 | example/debug.html 42 | -------------------------------------------------------------------------------- /csst.min.js: -------------------------------------------------------------------------------- 1 | !function(e){function t(e,t,o){function i(){p&&p.parentNode&&(p.parentNode.removeChild(p),p=null),f&&f.parentNode&&(f.parentNode.removeChild(f),f=null),r&&(clearTimeout(r),r=null),o=null}function d(){if(o){var e=getComputedStyle(f,!1),t=e.content,n=t.match(/[\w+=\/]+/);if(n)try{t=decodeURIComponent(escape(atob(n[0])))}catch(d){return void o(d)}o(null,t)}i()}"function"==typeof t&&(o=t,t={}),t=t||{};var r,l=t.prefix||"__csst",u=t.name||l+n++,a=t.param||"id",c="undefined"!=typeof t.timeout?c:null;c&&(r=setTimeout(function(){i(),o&&o(new Error("Timeout"))},c));var m=document.querySelector("head"),p=document.createElement("link"),f=document.createElement("span");return f.style.visibility="hidden",f.style.position="absolute",f.style.top="-100px",f.id=u,document.documentElement.appendChild(f),f.addEventListener("animationstart",d,!1),f.addEventListener("webkitAnimationStart",d,!1),e+=(e.indexOf("?")>=0?"&":"?")+a+"="+encodeURIComponent(u),e=e.replace("?&","?"),p.href=e,p.rel="stylesheet",p.type="text/css",m.appendChild(p),i}var n=0,o=t;"function"==typeof define?define.amd&&define(function(){return o}):"undefined"!=typeof module&&module.exports?module.exports=o:window[e]=o}("csst"); -------------------------------------------------------------------------------- /test/csst.js: -------------------------------------------------------------------------------- 1 | var csst = require("../");require("./lib/dom"); 2 | 3 | describe("src/csst.js", function () { 4 | var assert = require('should'); 5 | var util = require('util'); 6 | var examplejs_printLines; 7 | function examplejs_print() { 8 | examplejs_printLines.push(util.format.apply(util, arguments)); 9 | } 10 | 11 | 12 | 13 | it("csst():base", function (done) { 14 | examplejs_printLines = []; 15 | csst('http://localhost:2016/text.css?text=hello', function(err, text) { 16 | examplejs_print(err); 17 | assert.equal(examplejs_printLines.join("\n"), "null"); examplejs_printLines = []; 18 | 19 | examplejs_print(text); 20 | assert.equal(examplejs_printLines.join("\n"), "hello"); examplejs_printLines = []; 21 | done(); 22 | }); 23 | }); 24 | 25 | it("csst():base2", function (done) { 26 | examplejs_printLines = []; 27 | csst('http://localhost:2016/text.css?text=world', function(err, text) { 28 | examplejs_print(err); 29 | assert.equal(examplejs_printLines.join("\n"), "null"); examplejs_printLines = []; 30 | 31 | examplejs_print(text); 32 | assert.equal(examplejs_printLines.join("\n"), "world"); examplejs_printLines = []; 33 | done(); 34 | }); 35 | }); 36 | 37 | }); 38 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CSS Text Transformation Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csst", 3 | "version": "0.1.8", 4 | "description": "CSS Text Transformation", 5 | "main": "csst.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/zswang/csst.git" 9 | }, 10 | "keywords": [ 11 | "CSS", 12 | "Transformation", 13 | "CSST" 14 | ], 15 | "author": { 16 | "name": "zswang", 17 | "url": "http://weibo.com/zswang" 18 | }, 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/zswang/csst/issues" 22 | }, 23 | "homepage": "https://github.com/zswang/csst", 24 | "devDependencies": { 25 | "gulp": "^3.9.0", 26 | "gulp-open": "^2.0.0", 27 | "gulp-jdists": "^0.0.5", 28 | "gulp-rename": "^1.2.2", 29 | "gulp-uglify": "^1.5.3", 30 | "gulp-watch": "^4.3.5", 31 | "gulp-connect": "^4.0.0", 32 | "gulp-examplejs": "^0.0.9", 33 | "mocha": "^2.0.1", 34 | "istanbul": "^0.3.17", 35 | "should": "^4.1.0", 36 | "jshint": "^2.5.8" 37 | }, 38 | "dependencies": {}, 39 | "scripts": { 40 | "_update_version": "node version.js", 41 | "example": "gulp example", 42 | "dist": "npm run example && npm run _update_version && gulp && npm run test", 43 | "lint": "jshint src/*.js", 44 | "debug": "gulp debug", 45 | "mocha": "mocha", 46 | "test": "istanbul cover --hook-run-in-context node_modules/mocha/bin/_mocha -- -R spec" 47 | }, 48 | "files": [ 49 | "csst.js", 50 | "README.md" 51 | ] 52 | } -------------------------------------------------------------------------------- /example/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * 处理 CSST 样式 5 | * 6 | * @param {string} id 元素 ID 7 | * @param {string} text 传送文本 8 | * @return {string} 返回样式内容 9 | */ 10 | function csst(id, text) { 11 | if (typeof text !== 'string') { 12 | text = JSON.stringify(text); 13 | } 14 | text = new Buffer(text, 'utf-8').toString('base64') 15 | return ` 16 | @keyframes ${id} { 17 | from {} 18 | to { 19 | color: red; 20 | } 21 | } 22 | @-webkit-keyframes ${id} { 23 | from {} 24 | to { 25 | color: red; 26 | } 27 | } 28 | #${id} { 29 | content: "${text}"; 30 | animation: ${id} 2s; 31 | -webkit-animation: ${id} 2s; 32 | }`; 33 | } 34 | 35 | const http = require('http'); 36 | const url = require('url'); 37 | 38 | http.createServer(function(req, res) { 39 | console.log(req.url); // debug 40 | let location; 41 | try { 42 | location = url.parse(req.url, true); 43 | } catch (ex) { 44 | res.writeHead(503); 45 | res.end(ex.message); 46 | return; 47 | } 48 | 49 | switch (location.pathname) { 50 | case '/timestamp.css': 51 | // console.log(location); 52 | res.writeHead(200, { 53 | 'Content-Type': 'text/css' 54 | }); 55 | res.end(csst(location.query.id, { 56 | title: '时间戳\\u3031', 57 | value: Date.now().toString() 58 | })); 59 | break; 60 | default: 61 | res.writeHead(404); 62 | res.end('Not Found'); 63 | break; 64 | } 65 | }).listen('2016'); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /*jshint globalstrict: true*/ 2 | /*global require*/ 3 | 4 | 'use strict'; 5 | 6 | var gulp = require('gulp'); 7 | var util = require('util'); 8 | var jdists = require('gulp-jdists'); 9 | var uglify = require('gulp-uglify'); 10 | var rename = require('gulp-rename'); 11 | var connect = require('gulp-connect'); 12 | var open = require('gulp-open'); 13 | var examplejs = require('gulp-examplejs'); 14 | 15 | gulp.task('example', function() { 16 | return gulp.src('src/**/*.js') 17 | .pipe(examplejs({ 18 | header: 'var csst = require("../");require("./lib/dom");' 19 | })) 20 | .pipe(gulp.dest('test')); 21 | }); 22 | 23 | gulp.task('build', function() { 24 | return gulp.src(['src/csst.js']) 25 | .pipe(jdists({ 26 | trigger: 'release' 27 | })) 28 | .pipe(gulp.dest('./')) 29 | .pipe(uglify()) 30 | .pipe(rename('csst.min.js')) 31 | .pipe(gulp.dest('./')); 32 | }); 33 | 34 | gulp.task('buildDev', function() { 35 | return gulp.src(['src/csst.js']) 36 | .pipe(jdists({ 37 | trigger: 'debug' 38 | })) 39 | .pipe(gulp.dest('./')); 40 | }); 41 | 42 | var debugPort = 8111; 43 | 44 | function debugAddress() { 45 | var net = require('os').networkInterfaces(); 46 | var result; 47 | Object.keys(net).some(function(key) { 48 | return net[key].some(function(item) { 49 | if (!item.internal && item.family === 'IPv4') { 50 | result = item.address; 51 | return true; 52 | } 53 | }); 54 | }); 55 | return result; 56 | } 57 | 58 | gulp.task('connect', function() { 59 | require('./example/app'); 60 | connect.server({ 61 | root: './', 62 | port: debugPort, 63 | livereload: true 64 | }); 65 | }); 66 | 67 | gulp.task('html', function() { 68 | gulp.src('./example/*.html') 69 | .pipe(connect.reload()); 70 | }); 71 | 72 | gulp.task('open', function() { 73 | gulp.src(__filename) 74 | .pipe(open({ 75 | uri: util.format('http://%s:%s/example/index.html', debugAddress(), debugPort) 76 | })); 77 | }); 78 | 79 | gulp.task('watch', function() { 80 | gulp.watch(['src/*.js', 'example/*.html'], ['buildDev', 'html']); 81 | }); 82 | 83 | gulp.task('debug', ['buildDev', 'html', 'connect', 'open', 'watch']); 84 | 85 | gulp.task('default', ['build']); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CSST (CSS Text Transformation) 2 | ---------- 3 | 4 | # [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coverage Status][coverage-image]][coverage-url] 5 | 6 | ## 背景 7 | 8 | ### 什么是 CSST? 9 | 10 | 一种用 CSS 跨域传输文本的方案。相比 JSONP 更为安全,不需要执行跨站脚本。 11 | 12 | ## 原理 13 | 14 | 通过读取 CSS3 content 属性获取传送内容。 15 | 16 | ### 调用流程 17 | 18 | ![image](https://cloud.githubusercontent.com/assets/536587/15070367/63126c30-13b6-11e6-93aa-75bf5995c019.png) 19 | 20 | ### 技术手段 21 | 22 | * 怎么监听 `` 加载完毕? 23 | 24 | > 收集线上的资料,发现常见的方案是计时器或者用 onpropertychange、DOMAttrModified。 25 | 26 | > 考虑是 CSS3 场景,取巧用动画开始 (`animationstart`) 这个事件来捕获。 27 | 28 | * 怎么传送特殊字符("、'、\、\n、\r、\t)? 29 | 30 | > Chrome、Safari 对 `content` 样式属性字符解析并不一致 31 | 32 | > 为避免未知解析规则影响,统一使用 base64 编码 33 | 34 | ### 服务器应答的内容 35 | 36 | ```js 37 | function csst(id, text) { 38 | return ` 39 | @keyframes ${id} { 40 | from { 41 | } 42 | to { 43 | color: red; 44 | } 45 | } 46 | @-webkit-keyframes ${id} { 47 | from {} 48 | to { 49 | color: red; 50 | } 51 | } 52 | #${id} { 53 | content: ${new Buffer(text, 'utf-8').toString('base64')}; 54 | animation: ${id} 2s; 55 | -webkit-animation: ${id} 2s; 56 | }`; 57 | } 58 | ``` 59 | 60 | ## 与 JSONP 安全性比较 61 | 62 | ![image](https://cloud.githubusercontent.com/assets/536587/15090764/fcfee300-1465-11e6-9209-20d5ddd9b332.png) 63 | 64 | ## 开发 65 | 66 | ### 本机调试 67 | 68 | ```bash 69 | $ npm run debug 70 | ``` 71 | 72 | ### 构建 73 | 74 | ```bash 75 | $ npm run dist 76 | ``` 77 | 78 | ## 问题 79 | 80 | * 没有 JSONP 适配广,CSST 依赖支持 CSS3 的浏览器。 81 | 82 | ## 线上体验 83 | 84 | 85 | 86 | ## 参考资料 87 | 88 | * [link element onload](http://stackoverflow.com/questions/2635814/javascript-capturing-load-event-on-link) 89 | * [javascript: capturing load event on LINK](http://stackoverflow.com/questions/2635814/javascript-capturing-load-event-on-link) 90 | 91 | ## License 92 | 93 | MIT © [zswang](http://weibo.com/zswang) 94 | 95 | [npm-url]: https://npmjs.org/package/csst 96 | [npm-image]: https://badge.fury.io/js/csst.svg 97 | [travis-url]: https://travis-ci.org/zswang/csst 98 | [travis-image]: https://travis-ci.org/zswang/csst.svg?branch=master 99 | [coverage-url]: https://coveralls.io/github/zswang/csst?branch=master 100 | [coverage-image]: https://coveralls.io/repos/zswang/csst/badge.svg?branch=master&service=github 101 | -------------------------------------------------------------------------------- /csst.js: -------------------------------------------------------------------------------- 1 | (function (exportName) { 2 | /** 3 | * @file csst 4 | * 5 | * CSS Text Transformation 6 | * @author 7 | * zswang (http://weibo.com/zswang) 8 | * @version 0.1.8 9 | * @date 2016-07-23 10 | */ 11 | var count = 0; 12 | /** 13 | * 调用 CSST 14 | * 15 | * @param {string} url 服务连接 16 | * @param {Object|Function} opts/fn 配置项 17 | * @param {Function} fn 回调函数 18 | * @see https://github.com/webmodules/jsonp 19 | * @example csst():base 20 | ```js 21 | csst('http://localhost:2016/text.css?text=hello', function(err, text) { 22 | console.log(err); 23 | // > null 24 | console.log(text); 25 | // > hello 26 | // * done 27 | }); 28 | ``` 29 | * @example csst():base2 30 | ```js 31 | csst('http://localhost:2016/text.css?text=world', function(err, text) { 32 | console.log(err); 33 | // > null 34 | console.log(text); 35 | // > world 36 | // * done 37 | }); 38 | ``` 39 | */ 40 | function csst(url, opts, fn) { 41 | if ('function' == typeof opts) { 42 | fn = opts; 43 | opts = {}; 44 | } 45 | opts = opts || {}; 46 | var prefix = opts.prefix || '__csst'; 47 | // use the className name that was passed if one was provided. 48 | // otherwise generate a unique name by incrementing our counter. 49 | var id = opts.name || (prefix + (count++)); 50 | var param = opts.param || 'id'; 51 | var timeout = typeof opts.timeout !== 'undefined' ? timeout : null; 52 | var timer; 53 | if (timeout) { 54 | timer = setTimeout(function () { 55 | cleanup(); 56 | if (fn) { 57 | fn(new Error('Timeout')); 58 | } 59 | }, timeout); 60 | } 61 | function cleanup() { 62 | if (link && link.parentNode) { 63 | link.parentNode.removeChild(link); 64 | link = null; 65 | } 66 | if (span && span.parentNode) { 67 | span.parentNode.removeChild(span); 68 | span = null; 69 | } 70 | if (timer) { 71 | clearTimeout(timer); 72 | timer = null; 73 | } 74 | fn = null; 75 | } 76 | function handler() { 77 | if (fn) { 78 | var computedStyle = getComputedStyle(span, false); 79 | var content = computedStyle.content; 80 | var match = content.match(/[\w+=\/]+/); 81 | // base64 解码 82 | if (match) { 83 | try { 84 | content = decodeURIComponent(escape(atob(match[0]))); 85 | } 86 | catch (ex) { 87 | fn(ex); 88 | return; 89 | } 90 | } 91 | fn(null, content); 92 | } 93 | cleanup(); 94 | } 95 | var head = document.querySelector('head'); 96 | var link = document.createElement('link'); 97 | var span = document.createElement('span'); 98 | span.style.visibility = 'hidden'; 99 | span.style.position = 'absolute'; 100 | span.style.top = '-100px'; 101 | span.id = id; 102 | document.documentElement.appendChild(span); 103 | span.addEventListener('animationstart', handler, false); 104 | span.addEventListener('webkitAnimationStart', handler, false); 105 | url += (url.indexOf('?') >= 0 ? '&' : '?') + param + '=' + encodeURIComponent(id); 106 | url = url.replace('?&', '?'); 107 | link.href = url; 108 | link.rel = 'stylesheet'; 109 | link.type = 'text/css'; 110 | head.appendChild(link); 111 | return cleanup; 112 | } 113 | var exports = csst; 114 | /* istanbul ignore next */ 115 | if (typeof define === 'function') { 116 | if (define.amd) { 117 | define(function () { 118 | return exports; 119 | }); 120 | } 121 | } 122 | else if (typeof module !== 'undefined' && module.exports) { 123 | module.exports = exports; 124 | } 125 | else { 126 | window[exportName] = exports; 127 | } 128 | })('csst'); -------------------------------------------------------------------------------- /test/lib/dom.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const url = require('url'); 4 | var document; 5 | 6 | /** 7 | * 模拟 DOM 8 | */ 9 | class EventTarget { 10 | constructor() { 11 | this._listeners = []; 12 | } 13 | 14 | addEventListener(eventName, handler) { 15 | if (typeof handler === 'function') { 16 | this._listeners.push([eventName, handler]); 17 | } 18 | } 19 | 20 | dispatchEvent(eventName) { 21 | var eventData = { 22 | name: eventName 23 | }; 24 | var self = this; 25 | this._listeners.forEach(function(item) { 26 | if (item[0] === eventName) { 27 | item[1].call(self, eventData); 28 | } 29 | }); 30 | } 31 | } 32 | 33 | class Node extends EventTarget { 34 | constructor(nodeName) { 35 | super(); 36 | this._nodeName = nodeName; 37 | this._childNodes = []; 38 | } 39 | 40 | appendChild(node) { 41 | node.parentNode = this; 42 | this._childNodes.push(node); 43 | } 44 | 45 | removeChild(node) { 46 | this._childNodes = this._childNodes.filter(function(item) { 47 | return item !== node; 48 | });; 49 | } 50 | 51 | get childNodes() { 52 | return this._childNodes; 53 | } 54 | } 55 | 56 | class HTMLElement extends Node { 57 | constructor(tagName) { 58 | super(tagName); 59 | this._tagName = tagName; 60 | this._style = {}; 61 | this._id = undefined; 62 | } 63 | get style() { 64 | return this._style; 65 | } 66 | set id(value) { 67 | this._id = value; 68 | } 69 | get id() { 70 | return this._id; 71 | } 72 | } 73 | 74 | class HTMLHtmlElement extends HTMLElement { 75 | constructor() { 76 | super('html'); 77 | } 78 | } 79 | 80 | class HTMLHeadElement extends HTMLElement { 81 | constructor() { 82 | super('head'); 83 | } 84 | } 85 | 86 | class HTMLLinkElement extends HTMLElement { 87 | constructor() { 88 | super('link'); 89 | } 90 | set href(value) { 91 | this._href = value; 92 | let location = url.parse(value, true); 93 | var element = document.getElementById(location.query.id); 94 | element.computedStyle = { 95 | content: new Buffer(location.query.text).toString('base64') 96 | }; 97 | element.dispatchEvent('animationstart'); 98 | } 99 | } 100 | 101 | class HTMLSpanElement extends HTMLElement { 102 | constructor() { 103 | super('span'); 104 | } 105 | } 106 | 107 | class HTMLDocument extends Node { 108 | constructor() { 109 | super('#document'); 110 | this._documentElement = new HTMLHtmlElement(); 111 | this._head = new HTMLHeadElement(); 112 | this.appendChild(this._head); 113 | this.appendChild(this._documentElement); 114 | } 115 | 116 | querySelector(selector) { 117 | switch (selector) { 118 | case 'head': 119 | return this._head; 120 | } 121 | } 122 | 123 | createElement(tagName) { 124 | tagName = tagName.toLowerCase(); 125 | switch (tagName) { 126 | case 'link': 127 | return new HTMLLinkElement(); 128 | case 'span': 129 | return new HTMLSpanElement(); 130 | } 131 | return new HTMLElement(tagName); 132 | } 133 | 134 | get documentElement() { 135 | return this._documentElement; 136 | } 137 | 138 | getElementById(id) { 139 | var result; 140 | function scan(childNodes) { 141 | childNodes.every(function(node) { 142 | if (node.id === id) { 143 | result = node; 144 | } else { 145 | scan(node.childNodes); 146 | } 147 | return !result; 148 | }); 149 | } 150 | scan(this.childNodes); 151 | return result; 152 | } 153 | } 154 | 155 | class HTMLWindow extends EventTarget { 156 | constructor() { 157 | super(); 158 | this._document = new HTMLDocument(); 159 | } 160 | 161 | get document() { 162 | return this._document; 163 | } 164 | getComputedStyle(element, pseudoElt) { 165 | return element.computedStyle; 166 | } 167 | } 168 | 169 | var window = new HTMLWindow(); 170 | 171 | document = window.document; 172 | 173 | global.window = window; 174 | global.document = document; 175 | global.getComputedStyle = window.getComputedStyle; 176 | function atob(base64) { 177 | return new Buffer(base64, 'base64').toString(); 178 | } 179 | global.atob = atob; -------------------------------------------------------------------------------- /src/csst.js: -------------------------------------------------------------------------------- 1 | (function (exportName) { 2 | /**/ 3 | 'use strict'; 4 | /**/ 5 | 6 | /**/ 7 | /** 8 | * @file <%- name %> 9 | * 10 | * <%- description %> 11 | * @author 12 | <% (author instanceof Array ? author : [author]).forEach(function (item) { %> 13 | * <%- item.name %> (<%- item.url %>) 14 | <% }); %> 15 | * @version <%- version %> 16 | <% var now = new Date() %> 17 | * @date <%- [ 18 | now.getFullYear(), 19 | now.getMonth() + 101, 20 | now.getDate() + 100 21 | ].join('-').replace(/-1/g, '-') %> 22 | */ 23 | /**/ 24 | 25 | var count = 0; 26 | 27 | /** 28 | * 调用 CSST 29 | * 30 | * @param {string} url 服务连接 31 | * @param {Object|Function} opts/fn 配置项 32 | * @param {Function} fn 回调函数 33 | * @see https://github.com/webmodules/jsonp 34 | * @example csst():base 35 | ```js 36 | csst('http://localhost:2016/text.css?text=hello', function(err, text) { 37 | console.log(err); 38 | // > null 39 | 40 | console.log(text); 41 | // > hello 42 | // * done 43 | }); 44 | ``` 45 | * @example csst():base2 46 | ```js 47 | csst('http://localhost:2016/text.css?text=world', function(err, text) { 48 | console.log(err); 49 | // > null 50 | 51 | console.log(text); 52 | // > world 53 | // * done 54 | }); 55 | ``` 56 | */ 57 | function csst(url, opts, fn) { 58 | if ('function' == typeof opts) { 59 | fn = opts; 60 | opts = {}; 61 | } 62 | opts = opts || {}; 63 | 64 | var prefix = opts.prefix || '__csst'; 65 | 66 | // use the className name that was passed if one was provided. 67 | // otherwise generate a unique name by incrementing our counter. 68 | var id = opts.name || (prefix + (count++)); 69 | 70 | var param = opts.param || 'id'; 71 | var timeout = typeof opts.timeout !== 'undefined' ? timeout : null; 72 | var timer; 73 | 74 | if (timeout) { 75 | timer = setTimeout(function () { 76 | cleanup(); 77 | if (fn) { 78 | fn(new Error('Timeout')); 79 | } 80 | }, timeout); 81 | } 82 | 83 | function cleanup() { 84 | if (link && link.parentNode) { 85 | link.parentNode.removeChild(link); 86 | link = null; 87 | } 88 | if (span && span.parentNode) { 89 | span.parentNode.removeChild(span); 90 | span = null; 91 | } 92 | if (timer) { 93 | clearTimeout(timer); 94 | timer = null; 95 | } 96 | fn = null; 97 | } 98 | 99 | function handler() { 100 | if (fn) { 101 | var computedStyle = getComputedStyle(span, false); 102 | var content = computedStyle.content; 103 | 104 | /**/ 105 | console.log('content: %s', content); 106 | /**/ 107 | 108 | var match = content.match(/[\w+=\/]+/); 109 | // base64 解码 110 | if (match) { 111 | try { 112 | content = decodeURIComponent(escape(atob(match[0]))); 113 | } 114 | catch (ex) { 115 | fn(ex); 116 | return; 117 | } 118 | } 119 | fn(null, content); 120 | } 121 | cleanup(); 122 | } 123 | 124 | var head = document.querySelector('head'); 125 | var link = document.createElement('link'); 126 | var span = document.createElement('span'); 127 | span.style.visibility = 'hidden'; 128 | span.style.position = 'absolute'; 129 | span.style.top = '-100px'; 130 | span.id = id; 131 | document.documentElement.appendChild(span); 132 | 133 | span.addEventListener('animationstart', handler, false); 134 | span.addEventListener('webkitAnimationStart', handler, false); 135 | 136 | url += (url.indexOf('?') >= 0 ? '&' : '?') + param + '=' + encodeURIComponent(id); 137 | url = url.replace('?&', '?'); 138 | 139 | link.href = url; 140 | link.rel = 'stylesheet'; 141 | link.type = 'text/css'; 142 | 143 | head.appendChild(link); 144 | 145 | return cleanup; 146 | } 147 | 148 | var exports = csst; 149 | 150 | /* istanbul ignore next */ 151 | if (typeof define === 'function') { 152 | if (define.amd) { 153 | define(function () { 154 | return exports; 155 | }); 156 | } 157 | } 158 | else if (typeof module !== 'undefined' && module.exports) { 159 | module.exports = exports; 160 | } 161 | else { 162 | window[exportName] = exports; 163 | } 164 | 165 | })('csst'); --------------------------------------------------------------------------------