Skip to content

Commit

Permalink
feat(sass): support remote scss file
Browse files Browse the repository at this point in the history
Partially address #39
  • Loading branch information
3cp committed Jul 26, 2020
1 parent 20d5c3a commit 4f45742
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 42 deletions.
2 changes: 2 additions & 0 deletions client/nodejs-stubs.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import '@aurelia/kernel';

global.HOST_NAMES = require('./host-names');
global.DUMBER_MODULE_LOADER_DIST = 'dumber-module-loader dist content';
global.fetch = require('node-fetch');
22 changes: 12 additions & 10 deletions client/src-worker/transpiler.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import path from 'path';
import {inject} from 'aurelia-dependency-injection';
import {SvelteTranspiler} from './transpilers/svelte';
import {Au2Transpiler} from './transpilers/au2';
import {AuTsTranspiler} from './transpilers/au-ts';
Expand All @@ -7,17 +8,18 @@ import {SassTranspiler} from './transpilers/sass';
import {LessTranspiler} from './transpilers/less';
import {TextTranspiler} from './transpilers/text';

@inject(
SvelteTranspiler,
Au2Transpiler,
AuTsTranspiler,
JsTranspiler,
SassTranspiler,
LessTranspiler,
TextTranspiler
)
export class Transpiler {
constructor() {
this.transpilers = [
new SvelteTranspiler(),
new Au2Transpiler(),
new AuTsTranspiler(),
new JsTranspiler(),
new SassTranspiler(),
new LessTranspiler(),
new TextTranspiler()
];
constructor(...transpilers) {
this.transpilers = transpilers;
}

findTranspiler(file, files) {
Expand Down
130 changes: 115 additions & 15 deletions client/src-worker/transpilers/sass.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,79 @@
import path from 'path';
import {ext, parse, resolveModuleId} from 'dumber-module-loader/dist/id-utils';
import _ from 'lodash';
import {inject} from 'aurelia-dependency-injection';
import {CachePrimitives} from '../cache-primitives';

const EXTS = ['.scss', '.sass'];

function cleanSource(s) {
const idx = s.indexOf('/sass/');
if (idx === -1) return s;
return s.slice(idx + 6);
if (s.startsWith('../sass/')) {
return s.slice(8);
}
if (s.startsWith('../')) {
return s.slice(3);
}
return s;
}

export function possiblePaths(filePath) {
const parsed = parse(filePath);
const [packagePath, ...others] = parsed.parts;
if (others.length === 0) return [];

const base = others.pop();
const dir = _(others).map(o => o + '/').join('');

if (EXTS.indexOf(parsed.ext) !== -1 || parsed.ext === '.css') {
return {
packagePath,
filePaths: [
dir + base,
dir + base + '/_index.scss',
dir + base + '/_index.sass'
]
};
}

return {
packagePath,
filePaths: [
dir + base + '.scss',
dir + base + '.sass',
dir + base + '.css',
dir + '_' + base + '.scss',
dir + '_' + base + '.sass',
dir + base + '/_index.scss',
dir + base + '/_index.sass'
]
};
}

@inject(CachePrimitives)
export class SassTranspiler {
constructor(primitives) {
this.primitives = primitives;
}

match(file) {
const ext = path.extname(file.filename);
return EXTS.indexOf(ext) !== -1;
const e = ext(file.filename);
return EXTS.indexOf(e) !== -1;
}

async fetchRemoteFile(path) {
const possible = possiblePaths(path)

const packageJson = JSON.parse(
(await this.primitives.getJsdelivrFile(possible.packagePath, 'package.json')).contents
);

const packagePathWithVersion = possible.packagePath + '@' + packageJson.version;

for (const filePath of possible.filePaths) {
if (await this.primitives.doesJsdelivrFileExist(packagePathWithVersion, filePath)) {
return this.primitives.getJsdelivrFile(packagePathWithVersion, filePath);
}
}
throw new Error('No remote file found for ' + path);
}

_lazyLoad() {
Expand All @@ -21,7 +82,41 @@ export class SassTranspiler {
// https://github.com/sass/dart-sass/issues/25
// So I have to use sass.js (emscripted libsass) as it
// provided a fake fs layer.
this._promise = import('sass.js/dist/sass.sync');
this._promise = import('sass.js/dist/sass.sync').then(Sass => {
// Add custom importer to handle import from npm packages.
Sass.importer((request, done) => {
if (
request.path ||
request.current.startsWith('.') ||
request.current.match(/^https?:\/\//)
) {
// Sass.js already found a file,
// or it's definitely not a remote file,
// or it's a full url,
// let Sass.js to do its job.
done();
} else {
let remotePath = request.current;
if (request.previous.startsWith('/node_modules/')) {
remotePath = resolveModuleId(request.previous.slice(14), './' + request.current);
}

this.fetchRemoteFile(remotePath).then(
({path, contents}) => {
done({
path: '/node_modules/' + path.slice(23),
content: contents
});
},
err => {
done({error: err.message});
}
);
}
});

return Sass;
});
}

return this._promise;
Expand All @@ -30,32 +125,37 @@ export class SassTranspiler {
async transpile(file, files) {
const {filename} = file;
if (!this.match(file)) throw new Error('Cannot use SassTranspiler for file: ' + filename);
if (path.basename(filename).startsWith('_')) {

const parsed = parse(filename);
if (_.last(parsed.parts).startsWith('_')) {
// ignore sass partial
return;
}

const Sass = await this._lazyLoad();

const ext = path.extname(filename);

const cssFiles = {};
_.each(files, f => {
const ext = path.extname(f.filename);
if (EXTS.indexOf(ext) !== -1 || ext === '.css') {
const e = ext(f.filename);
if (EXTS.indexOf(e) !== -1 || e === '.css') {
cssFiles[f.filename] = f.content;
}
});

const newFilename = filename.slice(0, -ext.length) + '.css';
const newFilename = filename.slice(0, -parsed.ext.length) + '.css';
if (file.content.match(/^\s*$/)) {
return {filename: newFilename, content: ''};
}

return new Promise((resolve, reject) => {
Sass.writeFile(cssFiles, () => {
Sass.compileFile(
filename,
Sass.compile(
file.content,
{
indentedSyntax: parsed.ext === '.sass',
sourceMapRoot: '/',
inputPath: '/sass/' + filename
},
result => {
Sass.removeFile(Object.keys(cssFiles), () => {
if (result.status === 0) {
Expand Down
20 changes: 13 additions & 7 deletions client/test-worker/transpiler.spec.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import test from 'tape';
import {Container} from 'aurelia-dependency-injection';
import {Transpiler} from '../src-worker/transpiler';
import {CachePrimitives} from '../src-worker/cache-primitives';

const container = new Container();
container.registerInstance(CachePrimitives, {
async getJsdelivrFile() {
throw new Error('should not get here');
},
async doesJsdelivrFileExist() {
return false;
}
});
const jt = container.get(Transpiler);

const p = {
filename: 'package.json',
content: '{"dependencies":{"aurelia-bootstrapper":"1.0.0"}}'
};

test('Transpiler transpiles au1 ts file', async t => {
const jt = new Transpiler();
const code = `import {autoinject, bindable} from 'aurelia-framework';
@autoinject
export class Foo {
Expand All @@ -31,7 +43,6 @@ export class Foo {
});

test('Transpiler transpiles jsx file in inferno way with fragment', async t => {
const jt = new Transpiler();
const code = `const descriptions = items.map(item => (
<>
<dt>{item.name}</dt>
Expand All @@ -54,7 +65,6 @@ test('Transpiler transpiles jsx file in inferno way with fragment', async t => {
});

test('Transpiler transpile scss file', async t => {
const jt = new Transpiler();
const code = '.a { .b { color: red; } }';
const f = {
filename: 'src/foo.scss',
Expand All @@ -71,7 +81,6 @@ test('Transpiler transpile scss file', async t => {
});

test('Transpiler transpiles supported text file', async t => {
const jt = new Transpiler();
const code = 'lorem';
const file = await jt.transpile({
filename: 'src/foo/bar.html',
Expand All @@ -85,15 +94,13 @@ test('Transpiler transpiles supported text file', async t => {
});

test('Transpiler cannot transpile binary file', async t => {
const jt = new Transpiler();
t.equal(await jt.transpile({
filename: 'src/foo.jpg',
content: ''
}), undefined);
});

test('Transpiler transpiles au2 file', async t => {
const jt = new Transpiler();
const code = `export class Foo {
public name: string;
}
Expand All @@ -118,7 +125,6 @@ test('Transpiler transpiles au2 file', async t => {
});

test('eTranspiler transpiles svelte file with scss', async t => {
const jt = new Transpiler();
const code = `<script>
export let name;
</script>
Expand Down
Loading

0 comments on commit 4f45742

Please sign in to comment.