Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

the new API #59

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
"test": "test"
},
"dependencies": {
"generic-names": "^1.0.1",
"icss-replace-symbols": "1.0.2",
"postcss": "5.0.10",
"postcss-modules-values": "1.1.1",
"postcss-modules-extract-imports": "1.0.0",
"postcss-modules-local-by-default": "1.0.0",
"postcss-modules-scope": "1.0.0"
"postcss-modules-parser": "^1.1.0",
"postcss-modules-scope": "1.0.0",
"postcss-modules-values": "1.1.1"
},
"devDependencies": {
"babel": "5.8.29",
Expand Down
73 changes: 40 additions & 33 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,47 +20,54 @@ const traceKeySorter = ( a, b ) => {
};

export default class FileSystemLoader {
constructor( root, plugins ) {
this.root = root
this.sources = {}
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 = {}
this.trace = {}
}

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, depTrace ) {
return new Promise(( _resolve, _reject ) => {
const filename = /\w/i.test( to[0] )
? require.resolve( to )
: resolve( dirname( from ), to )

// 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);
}
catch (e) {}
if ( this.tokensByFile[filename] ) {
return void _resolve( this.tokensByFile[filename] )
}

let trace = this.trace[from] || String.fromCharCode( this.importNr++ )
if (typeof depTrace === 'number') {
trace += String.fromCharCode( depTrace )
}

const tokens = this.tokensByFile[fileRelativePath]
if (tokens) { return resolve(tokens) }
this.trace[filename] = trace

readFile( filename, 'utf8', (err, source) => {
if (err) {
return void _reject(err);
}

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

// https://github.com/postcss/postcss/blob/master/docs/api.md#lazywarnings
result.warnings().forEach(message => console.warn(message.text));

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[trace] = injectableSource
this.tokensByFile[fileRelativePath] = exportTokens
resolve( exportTokens )
}, reject )
_resolve( this.tokensByFile[filename] )
} )
.catch( _reject )
} )
} )
})
}

get finalSource() {
return Object.keys( this.sources ).sort( traceKeySorter ).map( s => this.sources[s] )
.join( "" )
.join( '' )
}
}
82 changes: 58 additions & 24 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,65 @@
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 { 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"
}