diff --git a/packages/configs/default/index.json b/packages/configs/default/index.json index 0e27411fd52..7914849d488 100644 --- a/packages/configs/default/index.json +++ b/packages/configs/default/index.json @@ -5,7 +5,8 @@ "@parcel/transformer-babel", "@parcel/transformer-js", "@parcel/transformer-terser" - ] + ], + "*.css": ["@parcel/transformer-css"] }, "namers": ["@parcel/namer-default"], "packagers": { diff --git a/packages/configs/default/package.json b/packages/configs/default/package.json index 0c85d4d4ae5..6cc209ff5ab 100644 --- a/packages/configs/default/package.json +++ b/packages/configs/default/package.json @@ -11,6 +11,7 @@ "@parcel/namer-default": "^2.0.0", "@parcel/packager-js": "^2.0.0", "@parcel/transformer-babel": "^2.0.0", + "@parcel/transformer-css": "^2.0.0", "@parcel/transformer-js": "^2.0.0", "@parcel/transformer-terser": "^2.0.0" } diff --git a/packages/core/types/index.js b/packages/core/types/index.js index 3cc734f9571..73bd23bcb6c 100644 --- a/packages/core/types/index.js +++ b/packages/core/types/index.js @@ -163,7 +163,8 @@ export type AssetOutput = { export type AST = { type: string, version: string, - program: any + program: any, + isDirty: boolean }; export type Config = any; diff --git a/packages/examples/simple/src/child.css b/packages/examples/simple/src/child.css new file mode 100644 index 00000000000..ad4f3016ada --- /dev/null +++ b/packages/examples/simple/src/child.css @@ -0,0 +1,3 @@ +h2 { + color: green; +} diff --git a/packages/examples/simple/src/index.js b/packages/examples/simple/src/index.js index bb9772db59a..8f9075c815f 100644 --- a/packages/examples/simple/src/index.js +++ b/packages/examples/simple/src/index.js @@ -1,3 +1,5 @@ +import styles from './styles.css'; + import('./async'); import('./async2'); diff --git a/packages/examples/simple/src/styles.css b/packages/examples/simple/src/styles.css new file mode 100644 index 00000000000..b1197af5fcc --- /dev/null +++ b/packages/examples/simple/src/styles.css @@ -0,0 +1,5 @@ +@import './child.css'; + +h1 { + color: gray; +} diff --git a/packages/resolvers/default/src/DefaultResolver.js b/packages/resolvers/default/src/DefaultResolver.js index 3507a19e785..508344ed0b3 100644 --- a/packages/resolvers/default/src/DefaultResolver.js +++ b/packages/resolvers/default/src/DefaultResolver.js @@ -12,7 +12,7 @@ import nodeBuiltins from 'node-libs-browser'; export default new Resolver({ async resolve(dep: Dependency, cli: CLIOptions, rootDir: string) { const resolved = await new NodeResolver({ - extensions: ['js', 'json'], + extensions: ['js', 'json', 'css'], cli, rootDir }).resolve(dep); diff --git a/packages/transformers/css/.eslintrc.json b/packages/transformers/css/.eslintrc.json new file mode 100644 index 00000000000..a75bb856758 --- /dev/null +++ b/packages/transformers/css/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": "@parcel/eslint-config", + "parserOptions": { + "sourceType": "module" + } +} diff --git a/packages/transformers/css/package.json b/packages/transformers/css/package.json new file mode 100644 index 00000000000..e6b410f4edd --- /dev/null +++ b/packages/transformers/css/package.json @@ -0,0 +1,19 @@ +{ + "name": "@parcel/transformer-css", + "version": "2.0.0", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/parcel-bundler/parcel.git" + }, + "main": "src/CSSTransformer.js", + "dependencies": { + "@parcel/plugin": "2.0.0", + "postcss": "^7.0.5", + "postcss-value-parser": "^3.3.1", + "semver": "^5.4.1" + }, + "devDependencies": { + "@parcel/eslint-config": "1.10.3" + } +} diff --git a/packages/transformers/css/src/CSSTransformer.js b/packages/transformers/css/src/CSSTransformer.js new file mode 100644 index 00000000000..25a38daa24c --- /dev/null +++ b/packages/transformers/css/src/CSSTransformer.js @@ -0,0 +1,135 @@ +// @flow +import {Transformer} from '@parcel/plugin'; +import postcss from 'postcss'; +import valueParser from 'postcss-value-parser'; +import semver from 'semver'; + +const URL_RE = /url\s*\("?(?![a-z]+:)/; +const IMPORT_RE = /@import/; +const PROTOCOL_RE = /^[a-z]+:/; + +function canHaveDependencies(asset) { + let {filePath, code} = asset; + return !/\.css$/.test(filePath) || IMPORT_RE.test(code) || URL_RE.test(code); +} + +function addURLDependency(asset, url: string, opts) { + asset.addDependency({ + moduleSpecifier: url, + isAsync: true, + ...opts + }); +} + +export default new Transformer({ + canReuseAST(ast) { + return ast.type === 'postcss' && semver.satisfies(ast.version, '^7.0.0'); + }, + + parse(asset) { + if (!canHaveDependencies(asset)) { + return null; + } + + return { + type: 'postcss', + version: '7.0.0', + isDirty: false, + program: postcss.parse(asset.code, { + from: asset.filePath, + to: asset.filePath + }) + }; + }, + + transform(asset) { + if (!asset.ast) { + return [asset]; + } + + asset.ast.program.walkAtRules('import', rule => { + let params = valueParser(rule.params); + let [name, ...media] = params.nodes; + let moduleSpecifier; + if ( + name.type === 'function' && + name.value === 'url' && + name.nodes.length + ) { + name = name.nodes[0]; + } + + moduleSpecifier = name.value; + + if (!moduleSpecifier) { + throw new Error('Could not find import name for ' + rule); + } + + if (PROTOCOL_RE.test(moduleSpecifier)) { + return; + } + + // If this came from an inline