-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
6,029 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
module.exports = { | ||
parser: "babel-eslint", | ||
parserOptions: { | ||
ecmaVersion: 2017, | ||
}, | ||
env: { | ||
es6: true | ||
}, | ||
|
||
extends: [ | ||
"eslint:recommended" | ||
], | ||
|
||
plugins: ["eslint-plugin-prettier"], | ||
|
||
rules: { | ||
"prettier/prettier": [ | ||
"error", | ||
{ | ||
useTabs: false, | ||
printWidth: 80, | ||
tabWidth: 2, | ||
singleQuote: false, | ||
trailingComma: "all", | ||
bracketSpacing: false, | ||
jsxBracketSameLine: false, | ||
parser: "babylon", | ||
semi: true | ||
} | ||
] | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
node_modules/ | ||
worker/worker.js | ||
fixtures/app/static | ||
yarn-error.log |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
language: node_js | ||
node_js: | ||
- "8" | ||
sudo: false | ||
before_install: | ||
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.7.0 | ||
- export PATH=$HOME/.yarn/bin:$PATH | ||
before_script: | ||
- yarn workspace css-to-js-sourcemap-worker run prepare | ||
- yarn workspace css-to-js-sourcemap-fixture-app run prepare | ||
script: | ||
- yarn run lint | ||
- yarn run test | ||
cache: | ||
yarn: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
/* eslint-env worker */ | ||
|
||
import {SourceMapConsumer} from "source-map/lib/source-map-consumer"; | ||
import ErrorStackParser from "error-stack-parser"; | ||
import SourceMapUrl from "source-map-url"; | ||
import {encode} from "sourcemap-codec"; | ||
|
||
class Token { | ||
constructor() { | ||
this.cancelled = false; | ||
} | ||
cancel() { | ||
this.cancelled = true; | ||
} | ||
} | ||
|
||
let invalidationToken = new Token(); | ||
|
||
function task(fn) { | ||
const token = invalidationToken; | ||
return result => { | ||
if (!token.cancelled) { | ||
return fn(result); | ||
} | ||
}; | ||
} | ||
|
||
const state = { | ||
mapperCache: new Map(), | ||
sourceCache: new Map(), | ||
inboundRequests: new Set(), | ||
// List of mapped class names for batch rendering | ||
renderQueue: [], | ||
}; | ||
|
||
export function initWasm(url) { | ||
SourceMapConsumer.initialize({ | ||
"lib/mappings.wasm": url, | ||
}); | ||
} | ||
|
||
export function renderCSS() { | ||
if (state.renderQueue.length === 0) { | ||
return ""; | ||
} | ||
const {rules, segments, sources} = state.renderQueue.reduce( | ||
(acc, {className, line, source}) => { | ||
let sourceIndex = acc.sources.indexOf(source); | ||
if (sourceIndex === -1) { | ||
sourceIndex = acc.sources.push(source) - 1; | ||
} | ||
acc.rules.push(`.${className} {}`); | ||
acc.segments.push([[0, sourceIndex, line - 1, 0]]); | ||
return acc; | ||
}, | ||
{rules: [], segments: [], sources: []}, | ||
); | ||
state.renderQueue = []; | ||
const mappings = encode(segments); | ||
|
||
const map = { | ||
version: 3, | ||
sources, | ||
mappings, | ||
sourcesContent: sources.map(source => state.sourceCache.get(source)), | ||
}; | ||
const json = JSON.stringify(map); | ||
const base64 = btoa(json); | ||
|
||
const comment = `/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64} */`; | ||
return `${rules.join("\n")}\n${comment}`; | ||
} | ||
|
||
export function addMappedClass({error, stackIndex, className}) { | ||
addMappedClassAsync({error, stackIndex, className}); | ||
} | ||
|
||
export function invalidate() { | ||
// Token should be immediately invalidated | ||
invalidationToken.cancel(); | ||
invalidationToken = new Token(); | ||
|
||
// After invalidation, existing mapped class names should be rendered | ||
// using the existing sourceCache | ||
const css = renderCSS(); | ||
|
||
state.mapperCache = new Map(); | ||
state.sourceCache = new Map(); | ||
|
||
// Replay inbound requests with cleared caches | ||
for (const request of state.inboundRequests) { | ||
addMappedClassAsync(request); | ||
} | ||
|
||
return css; | ||
} | ||
|
||
function addMappedClassAsync(request) { | ||
state.inboundRequests.add(request); | ||
const {error, stackIndex, className} = request; | ||
const location = getLocation(error, stackIndex); | ||
return getMapper(location.filename) | ||
.then( | ||
task(mapper => { | ||
const mapped = mapper.originalPositionFor(location); | ||
if (!state.sourceCache.has(mapped.source)) { | ||
state.sourceCache.set( | ||
mapped.source, | ||
mapper.sourceContentFor(mapped.source), | ||
); | ||
} | ||
state.renderQueue.push({ | ||
className, | ||
source: mapped.source, | ||
line: mapped.line, | ||
column: mapped.column, | ||
}); | ||
state.inboundRequests.remove(request); | ||
}), | ||
) | ||
.catch(err => { | ||
// eslint-disable-next-line no-console | ||
console.warn("Debug worker error", err); | ||
}); | ||
} | ||
|
||
function getIdentityMapper(sourceName, sourceContents) { | ||
return { | ||
originalPositionFor: ({line, column}) => ({ | ||
line, | ||
column, | ||
source: sourceName, | ||
}), | ||
sourceContentFor: () => { | ||
return sourceContents; | ||
}, | ||
}; | ||
} | ||
|
||
async function getMapper(filename) { | ||
const cached = state.mapperCache.get(filename); | ||
if (cached) { | ||
return cached; | ||
} | ||
|
||
const result = fetch(filename) | ||
.then( | ||
task(res => { | ||
return res.text(); | ||
}), | ||
) | ||
.then( | ||
task(src => { | ||
const url = SourceMapUrl.getFrom(src); | ||
return url ? getMapperFromUrl(url) : getIdentityMapper(filename, src); | ||
}), | ||
); | ||
|
||
state.mapperCache.set(filename, result); | ||
return result; | ||
} | ||
|
||
function getMapperFromUrl(url) { | ||
return getSourceMapJsonFromUrl(url).then( | ||
task(map => { | ||
return new SourceMapConsumer(map); | ||
}), | ||
); | ||
} | ||
|
||
function getLocation(error, stackIndex) { | ||
const frame = ErrorStackParser.parse(error)[stackIndex]; | ||
if (!frame.fileName) { | ||
throw new Error("Could not locate file"); | ||
} | ||
return { | ||
filename: frame.fileName, | ||
line: frame.lineNumber, | ||
column: frame.columnNumber, | ||
}; | ||
} | ||
|
||
async function getSourceMapJsonFromUrl(url) { | ||
return isDataUrl(url) | ||
? parseDataUrl(url) | ||
: fetch(url).then( | ||
task(res => { | ||
return res.json(); | ||
}), | ||
); | ||
} | ||
|
||
function isDataUrl(url) { | ||
return url.substr(0, 5) === "data:"; | ||
} | ||
|
||
function parseDataUrl(url) { | ||
const supportedEncodingRegexp = /^data:application\/json;([\w=:"-]+;)*base64,/; | ||
const match = url.match(supportedEncodingRegexp); | ||
if (match) { | ||
const sourceMapStart = match[0].length; | ||
const encodedSource = url.substr(sourceMapStart); | ||
const source = atob(encodedSource); | ||
return JSON.parse(source); | ||
} else { | ||
throw new Error("The encoding of the inline sourcemap is not supported"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"name": "css-to-js-sourcemap-core", | ||
"version": "1.0.0", | ||
"dependencies": { | ||
"error-stack-parser": "^2.0.1", | ||
"source-map": "^0.7.3", | ||
"source-map-url": "^0.4.0", | ||
"sourcemap-codec": "^1.4.1" | ||
}, | ||
"license": "MIT" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/* eslint-env browser */ | ||
|
||
window.worker = new Worker("/worker.js"); | ||
|
||
const err1 = new Error("Line 5"); | ||
const err2 = new Error("Line 6"); | ||
const err3 = new Error("Line 7"); | ||
|
||
window.error1 = toErrorLikeObject(err1); | ||
window.error2 = toErrorLikeObject(err2); | ||
window.error3 = toErrorLikeObject(err3); | ||
|
||
function toErrorLikeObject(err) { | ||
const {stack, stacktrace, message} = err; | ||
return {stack, stacktrace, message}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"name": "css-to-js-sourcemap-fixture-app", | ||
"version": "0.0.0-workspace", | ||
"private": true, | ||
"main": "server.js", | ||
"scripts": { | ||
"prepare": "webpack" | ||
}, | ||
"dependencies": { | ||
"sirv": "^0.1.2", | ||
"css-to-js-sourcemap-worker": "*" | ||
}, | ||
"devDependencies": { | ||
"webpack": ">= 4", | ||
"webpack-cli": "*" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* eslint-env node */ | ||
|
||
const http = require("http"); | ||
const path = require("path"); | ||
const sirv = require("sirv"); | ||
|
||
const workerPath = require.resolve("css-to-js-sourcemap-worker"); | ||
|
||
const assets = sirv(path.join(__dirname, "static")); | ||
const worker = sirv(path.dirname(workerPath)); | ||
|
||
const routes = { | ||
"/no-map": "/no-map.js", | ||
"/inline-map": "/inline-map.js", | ||
"/external-map": "/external-map.js", | ||
}; | ||
|
||
function createServer() { | ||
let blockNetwork = Promise.resolve(); | ||
let unblock = () => {}; | ||
const server = http.createServer((req, res) => { | ||
blockNetwork.then(() => { | ||
const script = routes[req.url]; | ||
if (script) { | ||
res.setHeader("Content-Type", "text/html"); | ||
res.statusCode = 200; | ||
return void res.end(template(script)); | ||
} | ||
if (req.url === "/worker.js") { | ||
return worker(req, res); | ||
} | ||
return assets(req, res); | ||
}); | ||
}); | ||
server.blockAllRequests = () => { | ||
blockNetwork = new Promise(resolve => { | ||
unblock = resolve; | ||
}); | ||
}; | ||
server.unblockAllRequests = () => { | ||
unblock(); | ||
}; | ||
return server; | ||
} | ||
|
||
module.exports = createServer; | ||
|
||
function template(url) { | ||
return `<!doctype html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"/> | ||
<script src="${url}"></script> | ||
</head> | ||
<body> | ||
</body> | ||
</html> | ||
`; | ||
} |
Oops, something went wrong.