├── .editorconfig ├── .eslintrc ├── .gitignore ├── README.md ├── autoprefixer.js ├── index.js ├── lib ├── BUILD_MODE.js ├── CSSLoader.js ├── CSSLoaderBuilded.js ├── CSSLoaderDOM.js ├── CSSModuleLoaderProcess.js └── plugins.js └── package.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "standard" 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSPM Loader: CSS 2 | 3 | [](https://gitter.im/geelen/jspm-loader-css?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | An extensible CSS loader for JSPM. 6 | 7 | Install the plugin and name it `css` locally 8 | 9 | ``` 10 | jspm install css=npm:jspm-loader-css 11 | ``` 12 | 13 | Load the styles by referencing them in your JS: 14 | 15 | ```js 16 | import from './styles.css!' 17 | ``` 18 | 19 | ## :local mode 20 | 21 | The default CSS loader supports opt-in CSS Modules syntax. So, importing the following CSS file: 22 | 23 | ```css 24 | :local(.myComponent) {} 25 | ``` 26 | 27 | generates and loads the following CSS 28 | 29 | ```css 30 | ._path_to_file__myComponent {} 31 | ``` 32 | 33 | and returns the mapping to JS for you to use in templates: 34 | 35 | ```js 36 | import styles from './styles.css!' 37 | elem.innerHTML = `
` 38 | ``` 39 | 40 | For the full CSS Modules syntax, where everything is local by default, see the [JSPM CSS Modules Loader](https://github.com/geelen/jspm-loader-css-modules) project. 41 | 42 | ## :export & :import 43 | 44 | The loader also supports the CSS Modules Interchange Format. 45 | 46 | ## Import path notation 47 | 48 | The path you specify will be processed through SystemJS with your [configuration](https://github.com/systemjs/systemjs/blob/master/docs/config-api.md). 49 | For example, with the configuration below : 50 | 51 | ```js 52 | // Your config.js 53 | System.config({ 54 | paths: { 55 | "github:*": "jspm_packages/github/*", 56 | "~/*": "somewhere/*" 57 | } 58 | } 59 | ``` 60 | 61 | You can write various import paths: 62 | 63 | ```css 64 | /* Your ike.icss */ 65 | .ike { 66 | composes: bounce animated from "https://github.jspm.io/daneden/animate.css@3.1.0/animate.css"; 67 | composes: bounce animated from "github:daneden/animate.css@3.1.0/animate.css"; 68 | composes: bounce animated from "~/animate.css"; 69 | } 70 | ``` 71 | 72 | ## Customize your own loader 73 | 74 | You can customize this loader to meet your needs. 75 | 76 | 1. Create a `css.js` file under your project folder next to `config.js` file. 77 | 2. In the `css.js` file, include whatever postcss plugins you need: 78 | 79 | ```js 80 | import { CSSLoader, Plugins } from 'jspm-loader-css' 81 | import vars from 'postcss-simple-vars' // you want to use this postcss plugin 82 | 83 | const {fetch, bundle} = new CSSLoader([ 84 | vars, 85 | Plugins.localByDefault, 86 | Plugins.extractImports, 87 | Plugins.scope, 88 | Plugins.autoprefixer() 89 | ], __moduleName); 90 | 91 | export {fetch, bundle}; 92 | ``` 93 | 94 | Just make sure you have `Plugins.autoprefixer` passed to `new CSSLoader`, it's required. 95 | 96 | 3. Since you have had `jspm-loader-css` installed with `jspm install css=npm:jspm-loader-css`, now open `config.js` and replace line 97 | 98 | ```js 99 | "css": "npm:jspm-loader-css@x.x.x" 100 | ``` 101 | 102 | with: 103 | 104 | ```js 105 | "jspm-loader-css": "npm:jspm-loader-css@x.x.x" 106 | ``` 107 | 108 | jspm will use what `css.js` exports as the default css loader. 109 | 110 | You can also check [an example css.js file here](https://github.com/geelen/glenmaddern.com/blob/master/src/css.js "Customize your own jspm css loader"). 111 | 112 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import {Plugins} from './lib/plugins' 4 | import {CSSLoader} from './lib/CSSLoader' 5 | 6 | const {fetch, bundle} = new CSSLoader([ 7 | Plugins.values, 8 | Plugins.extractImports, 9 | Plugins.scope, 10 | Plugins.autoprefixer() 11 | ]) 12 | 13 | export {CSSLoader, Plugins, fetch, bundle} 14 | -------------------------------------------------------------------------------- /lib/BUILD_MODE.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | export const BUILD_MODE = typeof window === 'undefined' 4 | -------------------------------------------------------------------------------- /lib/CSSLoader.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import {CSSLoaderBuilded} from './CSSLoaderBuilded' 4 | import {CSSLoaderDOM} from './CSSLoaderDOM' 5 | import {BUILD_MODE} from './BUILD_MODE' 6 | 7 | // 8 | 9 | const LoaderExtention = BUILD_MODE ? CSSLoaderBuilded : CSSLoaderDOM 10 | 11 | export {LoaderExtention as CSSLoader} 12 | 13 | -------------------------------------------------------------------------------- /lib/CSSLoaderBuilded.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import {CSSModuleLoaderProcess} from './CSSModuleLoaderProcess' 4 | 5 | const CSS_INJECT_FUNCTION = "(function(c){if (typeof document == 'undefined') return; var d=document,a='appendChild',i='styleSheet',s=d.createElement('style');s.type='text/css';d.getElementsByTagName('head')[0][a](s);s[i]?s[i].cssText=c:s[a](d.createTextNode(c));})" 6 | const EMPTY_SYSTEM_REGISTER = (system, name) => `${system}.register('${name}', [], function() { return { setters: [], execute: function() {}}});` 7 | 8 | export class CSSLoaderBuilded extends CSSModuleLoaderProcess { 9 | constructor (plugins, moduleName) { 10 | super(plugins, moduleName) 11 | 12 | // enforce this on exported functions 13 | this.bundle = this.bundle.bind(this) 14 | } 15 | 16 | fetch (load, systemFetch) { 17 | return super.fetch(load, systemFetch) 18 | // Return the export tokens to the js files 19 | .then((styleSheet) => styleSheet.exportedTokens) 20 | } 21 | 22 | bundle (loads, compileOpts) { 23 | const fileDefinitions = loads 24 | .map((load) => EMPTY_SYSTEM_REGISTER(compileOpts.systemGlobal || 'System', load.name)) 25 | .join('\n') 26 | 27 | return ` 28 | // Fake file definitions 29 | ${fileDefinitions} 30 | // Inject all the css 31 | ${CSS_INJECT_FUNCTION} 32 | ('${this._getAllSources()}');` 33 | } 34 | 35 | _getAllSources () { 36 | const sortedDependencies = this._getSortedStylesDependencies() 37 | return sortedDependencies 38 | .map((depName) => this._styleMeta.get(depName).injectableSource) 39 | .join('\n') 40 | .replace(/(['\\])/g, '\\$1') 41 | .replace(/[\f]/g, '\\f') 42 | .replace(/[\b]/g, '\\b') 43 | .replace(/[\n]/g, '\\n') 44 | .replace(/[\t]/g, '\\t') 45 | .replace(/[\r]/g, '\\r') 46 | .replace(/[\u2028]/g, '\\u2028') 47 | .replace(/[\u2029]/g, '\\u2029') 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/CSSLoaderDOM.js: -------------------------------------------------------------------------------- 1 | // 2 | /* eslint-env browser */ 3 | 4 | import debounce from 'debounce' 5 | 6 | import {BUILD_MODE} from './BUILD_MODE' 7 | import {CSSModuleLoaderProcess} from './CSSModuleLoaderProcess' 8 | 9 | const USE_STYLE_TAGS = BUILD_MODE || !window.Blob || !window.URL || !URL.createObjectURL || navigator.userAgent.match(/phantomjs/i) 10 | 11 | export class CSSLoaderDOM extends CSSModuleLoaderProcess { 12 | constructor (plugins, moduleName) { 13 | super(plugins, moduleName) 14 | 15 | this._head = document.getElementsByTagName('head')[0] 16 | this._jspmCssModulesContainer = document.createElement('jspm-css-modules') 17 | this._debouncedSortingAndInject = debounce(this._sortAndInjectInDom, 500).bind(this) 18 | 19 | this._head.appendChild(this._jspmCssModulesContainer) 20 | } 21 | 22 | fetch (load, systemFetch) { 23 | let styleSheet 24 | return super.fetch(load, systemFetch) 25 | .then((_styleSheet) => styleSheet = _styleSheet) 26 | .then(this._storeInBlobs) 27 | // Debounce dep sorting and injection in the DOM 28 | .then(this._debouncedSortingAndInject) 29 | // Return the export tokens to the js files 30 | .then(() => styleSheet.exportedTokens) 31 | } 32 | 33 | _storeInBlobs (styleSheet) { 34 | if (USE_STYLE_TAGS) { 35 | return styleSheet 36 | } 37 | 38 | const blob = new Blob([styleSheet.injectableSource], { type: 'text/css' }) 39 | const blobUrl = URL.createObjectURL(blob) 40 | styleSheet.blobUrl = blobUrl 41 | 42 | return styleSheet 43 | } 44 | 45 | _sortAndInjectInDom (styleSheet) { 46 | const sortedDependencies = this._getSortedStylesDependencies() 47 | 48 | const styleTagsInjection = (depName) => `` 49 | const linkTagsInjection = (depName) => `` 50 | const injectionFunc = USE_STYLE_TAGS ? styleTagsInjection : linkTagsInjection 51 | 52 | this._jspmCssModulesContainer.innerHTML = sortedDependencies 53 | .map(injectionFunc) 54 | .join('') 55 | 56 | return styleSheet 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/CSSModuleLoaderProcess.js: -------------------------------------------------------------------------------- 1 | // 2 | /* global __moduleName */ 3 | 4 | import CssModulesLoaderCore from 'css-modules-loader-core' 5 | import toposort from 'toposort' 6 | import path from 'path' 7 | 8 | export class CSSModuleLoaderProcess { 9 | constructor (plugins, moduleName) { 10 | this._moduleName = moduleName || __moduleName 11 | this._cssModulesLoader = new CssModulesLoaderCore(plugins) 12 | 13 | this._stylesDependencyTree = new Set() 14 | this._stylesDependencies = new Set() 15 | this._styleMeta = new Map() 16 | 17 | // enforce this on exported functions 18 | this.fetch = this.fetch.bind(this) 19 | } 20 | 21 | fetch (load, systemFetch) { 22 | const sourcePath = this._extractCanonicalPathFromImportPath(load.address) 23 | 24 | const styleSheet = { 25 | name: sourcePath, 26 | injectableSource: null, 27 | exportedTokens: null 28 | } 29 | 30 | this._stylesDependencies.add(sourcePath) 31 | this._styleMeta.set(sourcePath, styleSheet) 32 | 33 | return systemFetch(load) 34 | .then((source) => 35 | this._cssModulesLoader.load(source, sourcePath, '', this._fetchDependencies.bind(this)) 36 | ) 37 | .then(({ injectableSource, exportTokens }) => { 38 | styleSheet.exportedTokens = this._styleExportModule(exportTokens) 39 | styleSheet.injectableSource = injectableSource 40 | return styleSheet 41 | }) 42 | } 43 | 44 | _fetchDependencies (_newPath, relativeTo) { 45 | // Figure out the path that System will need to find the right file, 46 | // and trigger the import (which will instantiate this loader once more) 47 | const newPath = _newPath.replace(/^["']|["']$/g, '') 48 | const canonicalParent = relativeTo.replace(/^\//, '') 49 | 50 | return System.normalize(newPath + '!', path.join(System.baseURL, canonicalParent)) 51 | .then((importPath) => { 52 | const canonicalPath = this._extractCanonicalPathFromImportPath(importPath) 53 | this._stylesDependencyTree.add([canonicalParent, canonicalPath]) 54 | return importPath 55 | }) 56 | .then(System.import.bind(System)) 57 | .then((exportedTokens) => exportedTokens.default || exportedTokens) 58 | } 59 | 60 | _extractCanonicalPathFromImportPath (importPath) { 61 | return (importPath.startsWith(System.baseURL) 62 | ? path.relative(System.baseURL, importPath) 63 | : importPath) 64 | .replace(/!.*$/, '') 65 | } 66 | 67 | _styleExportModule (exportTokens) { 68 | return `module.exports = ${JSON.stringify(exportTokens)}` 69 | } 70 | 71 | _getSortedStylesDependencies () { 72 | return toposort.array( 73 | Array.from(this._stylesDependencies), 74 | Array.from(this._stylesDependencyTree) 75 | ) 76 | .reverse() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/plugins.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import autoprefixer from '../autoprefixer' 4 | import Core from 'css-modules-loader-core' 5 | 6 | export const Plugins = { 7 | values: Core.values, 8 | localByDefault: Core.localByDefault, 9 | extractImports: Core.extractImports, 10 | scope: Core.scope, 11 | autoprefixer 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jspm-loader-css", 3 | "main": "index", 4 | "format": "es6", 5 | "version": "1.1.0", 6 | "description": "A CSS loader for JSPM. Install", 7 | "dependencies": { 8 | "css-modules-loader-core": "^1.0.0-beta2", 9 | "debounce": "^1.0.0", 10 | "path": "^0.12.7", 11 | "toposort": "^0.2.12" 12 | }, 13 | "devDependencies": { 14 | "babel-eslint": "^4.1.3", 15 | "eslint": "^1.5.0", 16 | "eslint-config-standard": "^4.4.0", 17 | "eslint-plugin-standard": "^1.3.1" 18 | }, 19 | "scripts": { 20 | "test": "echo \"Error: no test specified\" && exit 1", 21 | "lint": "eslint index.js lib" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/geelen/jspm-loader-css.git" 26 | }, 27 | "keywords": [ 28 | "jspm", 29 | "css", 30 | "css-modules", 31 | "loader" 32 | ], 33 | "author": "Glen Maddern", 34 | "license": "ISC", 35 | "bugs": { 36 | "url": "https://github.com/geelen/jspm-loader-css/issues" 37 | }, 38 | "homepage": "https://github.com/geelen/jspm-loader-css" 39 | } 40 | --------------------------------------------------------------------------------