Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
btd committed Oct 18, 2016
1 parent b27907f commit ae19a8d
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 176 deletions.
15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
"test": "test"
},
"dependencies": {
"icss-replace-symbols": "1.0.2",
"postcss": "5.2.0",
"postcss-modules-values": "1.2.2",
"postcss-modules-extract-imports": "1.0.0",
"postcss-modules-local-by-default": "1.1.1",
"postcss-modules-scope": "1.0.2"
"generic-names": "^1.0.2",
"icss-replace-symbols": "^1.0.2",
"lodash.partialright": "^4.2.1",
"postcss": "^5.2.0",
"postcss-modules-extract-imports": "^1.0.0",
"postcss-modules-local-by-default": "^1.1.1",
"postcss-modules-parser": "^1.1.0",
"postcss-modules-scope": "^1.0.2",
"postcss-modules-values": "1.2.2"
},
"devDependencies": {
"babel": "5.8.29",
Expand Down
74 changes: 30 additions & 44 deletions src/file-system-loader.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Core from './index.js'
import fs from 'fs'
import path from 'path'
import core from './index'
import { readFile } from 'fs'
import { dirname, resolve } from 'path'

// Sorts dependencies in the following way:
// AAA comes before AA and A
Expand All @@ -20,58 +20,44 @@ const traceKeySorter = ( a, b ) => {
};

export default class FileSystemLoader {
constructor( root, plugins ) {
this.root = root
this.sources = {}
this.traces = {}

constructor( options, processorOptions = {} ) {
this.processorOptions = processorOptions
this.core = core( options, this.fetch.bind(this) )
this.importNr = 0
this.core = new Core(plugins)
this.tokensByFile = {};
this.sources = {}
this.tokensByFile = {}
}

fetch( _newPath, relativeTo, _trace ) {
let newPath = _newPath.replace( /^["']|["']$/g, "" ),
trace = _trace || String.fromCharCode( this.importNr++ )
return new Promise( ( resolve, reject ) => {
let relativeDir = path.dirname( relativeTo ),
rootRelativePath = path.resolve( relativeDir, newPath ),
fileRelativePath = path.resolve( path.join( this.root, relativeDir ), newPath )
fetch( _to, from ) {
let to = _to.replace( /^["']|["']$/g, '' ),
trace = String.fromCharCode( this.importNr++ )

// if the path is not relative or absolute, try to resolve it in node_modules
if (newPath[0] !== '.' && newPath[0] !== '/') {
try {
fileRelativePath = require.resolve(newPath);
const filename = /\w/i.test(to[0])
? require.resolve(to)
: resolve(dirname(from), to)

return new Promise(( resolve, reject ) => {
readFile( filename, 'utf8', (err, source) => {
if (err) {
return void reject(err);
}
catch (e) {}
}

const tokens = this.tokensByFile[fileRelativePath]
if (tokens) { return resolve(tokens) }
this.core.process( source, Object.assign( this.processorOptions, { from: filename } ) )
.then( result => {
this.sources[filename] = result.css
this.tokensByFile[filename] = result.root.tokens

fs.readFile( fileRelativePath, "utf-8", ( err, source ) => {
if ( err ) reject( err )
this.core.load( source, rootRelativePath, trace, this.fetch.bind( this ) )
.then( ( { injectableSource, exportTokens } ) => {
this.sources[fileRelativePath] = injectableSource
this.traces[trace] = fileRelativePath
this.tokensByFile[fileRelativePath] = exportTokens
resolve( exportTokens )
}, reject )
resolve( this.tokensByFile[filename] )
} )
.catch( reject )
} )
} )
})
}

get finalSource() {
const traces = this.traces
const sources = this.sources
let written = new Set()

return Object.keys( traces ).sort( traceKeySorter ).map(key => {
const filename = traces[key]
if (written.has(filename)) { return null }
written.add(filename)

return sources[filename];
}).join( "" )
return Object.keys( this.sources ).sort( traceKeySorter ).map( s => this.sources[s] )
.join( '' )
}
}
83 changes: 59 additions & 24 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,66 @@
import postcss from 'postcss'
import localByDefault from 'postcss-modules-local-by-default'
import extractImports from 'postcss-modules-extract-imports'
import scope from 'postcss-modules-scope'
import values from 'postcss-modules-values'
import postcss from 'postcss';
import genericNames from 'generic-names';
import partialRight from 'lodash.partialright';
import { relative } from 'path';

import Parser from './parser'
import LocalByDefault from 'postcss-modules-local-by-default'
import ExtractImports from 'postcss-modules-extract-imports'
import Scope from 'postcss-modules-scope'
import Parser from 'postcss-modules-parser'
import Values from 'postcss-modules-values'

export default class Core {
constructor( plugins ) {
this.plugins = plugins || Core.defaultPlugins
}
/**
* @param {array} options.append
* @param {array} options.prepend
* @param {array} options.use
* @param {function} options.createImportedName
* @param {function|string} options.generateScopedName
* @param {string} options.mode
* @param {string} options.rootDir
* @param {function} fetch
* @return {object}
*/
export default function core({
append = [],
prepend = [],
createImportedName,
generateScopedName: scopedName,
rootDir: context = process.cwd(),
mode,
use,
} = {}, _fetch) {
let instance
let generateScopedName

load( sourceString, sourcePath, trace, pathFetcher ) {
let parser = new Parser( pathFetcher, trace )
const fetch = function () {
return _fetch.apply(null, Array.prototype.slice.call(arguments).concat(instance));
}

return postcss( this.plugins.concat( [parser.plugin] ) )
.process( sourceString, { from: "/" + sourcePath } )
.then( result => {
return { injectableSource: result.css, exportTokens: parser.exportTokens }
} )
if (scopedName) {
// https://github.com/css-modules/postcss-modules-scope/blob/master/src/index.js#L38
generateScopedName = typeof scopedName !== 'function'
? genericNames(scopedName || '[name]__[local]___[hash:base64:5]', {context})
: (local, filepath, css) => scopedName(local, filepath, css, context)
} else {
generateScopedName = (localName, filepath) => {
return Scope.generateScopedName(localName, relative(context, filepath));
}
}
}

// These four plugins are aliased under this package for simplicity.
Core.values = values
Core.localByDefault = localByDefault
Core.extractImports = extractImports
Core.scope = scope
const plugins = (use || [
...prepend,
Values,
mode
? new LocalByDefault({mode})
: LocalByDefault,
createImportedName
? new ExtractImports({createImportedName})
: ExtractImports,
new Scope({generateScopedName}),
...append,
])
.concat(new Parser({fetch})) // no pushing in order to avoid the possible mutations

Core.defaultPlugins = [values, localByDefault, extractImports, scope]
instance = postcss(plugins)
return instance;
}
63 changes: 0 additions & 63 deletions src/parser.js

This file was deleted.

62 changes: 34 additions & 28 deletions test/test-cases.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
"use strict";

import assert from "assert"
import fs from "fs"
import path from "path"
import FileSystemLoader from "../src/file-system-loader"
import assert from 'assert'
import { existsSync, readdirSync, readFileSync } from 'fs'
import { join } from 'path'
import FileSystemLoader from '../src/file-system-loader'

let normalize = ( str ) => {
return str.replace( /\r\n?/g, "\n" );
return str.replace( /\r\n?/g, '\n' );
}

const pipelines = {
"test-cases": undefined,
"cssi": []
'test-cases': undefined,
'cssi': []
}

Object.keys( pipelines ).forEach( dirname => {
describe( dirname, () => {
let testDir = path.join( __dirname, dirname )
fs.readdirSync( testDir ).forEach( testCase => {
if ( fs.existsSync( path.join( testDir, testCase, "source.css" ) ) ) {
it( "should " + testCase.replace( /-/g, " " ), done => {
let expected = normalize( fs.readFileSync( path.join( testDir, testCase, "expected.css" ), "utf-8" ) )
let loader = new FileSystemLoader( testDir, pipelines[dirname] )
let expectedTokens = JSON.parse( fs.readFileSync( path.join( testDir, testCase, "expected.json" ), "utf-8" ) )
loader.fetch( `${testCase}/source.css`, "/" ).then( tokens => {
assert.equal( loader.finalSource, expected )
assert.equal( JSON.stringify( tokens ), JSON.stringify( expectedTokens ) )
} ).then( done, done )
let testDir = join( __dirname, dirname )
readdirSync( testDir ).forEach( testCase => {
if ( existsSync( join( testDir, testCase, 'source.css' ) ) ) {
it( 'should ' + testCase.replace( /-/g, ' ' ), done => {
const loader = new FileSystemLoader({rootDir: testDir, use: pipelines[dirname]})

let expected = normalize( readFileSync( join( testDir, testCase, 'expected.css' ), 'utf-8' ) )
let expectedTokens = JSON.parse( readFileSync( join( testDir, testCase, 'expected.json' ), 'utf-8' ) )
let filepath = join(testDir, testCase, 'source.css')

loader.fetch(filepath, filepath, null)
.then( tokens => {
assert.equal( loader.finalSource, expected )
assert.equal( JSON.stringify( tokens ), JSON.stringify( expectedTokens ) )
} ).then( done, done )
} );
}
} );
Expand All @@ -35,16 +37,20 @@ Object.keys( pipelines ).forEach( dirname => {

// special case for testing multiple sources
describe( 'multiple sources', () => {
let testDir = path.join( __dirname, 'test-cases' )
let testDir = join( __dirname, 'test-cases' )
let testCase = 'multiple-sources';
let dirname = 'test-cases';
if ( fs.existsSync( path.join( testDir, testCase, "source1.css" ) ) ) {
it( "should " + testCase.replace( /-/g, " " ), done => {
let expected = normalize( fs.readFileSync( path.join( testDir, testCase, "expected.css" ), "utf-8" ) )
let loader = new FileSystemLoader( testDir, pipelines[dirname] )
let expectedTokens = JSON.parse( fs.readFileSync( path.join( testDir, testCase, "expected.json" ), "utf-8" ) )
loader.fetch( `${testCase}/source1.css`, "/" ).then( tokens1 => {
loader.fetch( `${testCase}/source2.css`, "/" ).then( tokens2 => {
if ( existsSync( join( testDir, testCase, 'source1.css' ) ) ) {
it( 'should ' + testCase.replace( /-/g, ' ' ), done => {
const loader = new FileSystemLoader({rootDir: testDir, use: pipelines[dirname]})

let expected = normalize( readFileSync( join( testDir, testCase, 'expected.css' ), 'utf-8' ) )
let expectedTokens = JSON.parse( readFileSync( join( testDir, testCase, 'expected.json' ), 'utf-8' ) )
let filepath1 = join(testDir, testCase, 'source1.css')
let filepath2 = join(testDir, testCase, 'source2.css')

loader.fetch( filepath1, filepath1, null ).then( tokens1 => {
loader.fetch( filepath2, filepath2, null ).then( tokens2 => {
assert.equal( loader.finalSource, expected )
const tokens = Object.assign({}, tokens1, tokens2);
assert.equal( JSON.stringify( tokens ), JSON.stringify( expectedTokens ) )
Expand Down
2 changes: 1 addition & 1 deletion test/test-cases/compose-node-module/expected.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
._compose_node_module_cool_styles_foo__example {
._node_modules_cool_styles_foo__example {
color: #F00;
}
._compose_node_module_source__foo {
Expand Down
2 changes: 1 addition & 1 deletion test/test-cases/compose-node-module/expected.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"foo": "_compose_node_module_source__foo _compose_node_module_cool_styles_foo__example"
"foo": "_compose_node_module_source__foo _node_modules_cool_styles_foo__example"
}
12 changes: 6 additions & 6 deletions test/test-cases/multiple-dependencies/expected.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
._multiple_dependencies_d__d1 {
color: #d1d1d1;
}
._multiple_dependencies_d__d2 {
color: #d2d2d2;
}
._multiple_dependencies_b__b1 {
color: #b1b1b1;
}
Expand All @@ -14,6 +8,12 @@
._multiple_dependencies_c__c {
color: #ccc;
}
._multiple_dependencies_d__d1 {
color: #d1d1d1;
}
._multiple_dependencies_d__d2 {
color: #d2d2d2;
}
._multiple_dependencies_source__a {
color: #aaa;
}
Expand Down
Loading

0 comments on commit ae19a8d

Please sign in to comment.