├── .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 |
14 | -
15 | #{{ item.id }} : {{ item.timestamp }}
16 |
17 |
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 | 
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 | 
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');
--------------------------------------------------------------------------------