├── CHANGELOG.md
├── demo
├── .gitignore
├── .babelrc
├── components
│ ├── common.less
│ ├── Wrap.js
│ ├── Wrap.less
│ ├── App.js
│ └── App.less
├── css-flat.config.js
├── index.js
├── index.html
├── build.sh
├── package.json
└── webpack.config.js
├── index.js
├── .gitattributes
├── .gitignore
├── test
├── css-flat.config.js
├── moduleTestCases
│ └── class-names
│ │ ├── colors.css
│ │ ├── test.png
│ │ ├── expected.css
│ │ └── source.css
├── moduleMinimizeTestCases
│ └── keyframes-and-animation
│ │ ├── expected.css
│ │ └── source.css
├── moduleTest.js
├── moduleMinimizeTest.js
└── helpers.js
├── .editorconfig
├── .travis.yml
├── src
├── declValueMap.js
├── error.js
├── pseudoMap.js
├── getLoaderConfig.js
├── getSelectorType.js
├── getSelectorName.js
├── declPropMap.js
├── loader.js
└── processCss.js
├── LICENSE
├── package.json
└── README.md
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require("./src/loader");
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | yarn.lock -diff
2 | * text=auto
3 | bin/* eol=lf
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | npm-debug.log
4 | .idea
5 |
--------------------------------------------------------------------------------
/test/css-flat.config.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = { plugins: [] }
3 |
--------------------------------------------------------------------------------
/demo/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/demo/components/common.less:
--------------------------------------------------------------------------------
1 | @red: red;
2 | .title1 {
3 | color: #666666;
4 | }
--------------------------------------------------------------------------------
/test/moduleTestCases/class-names/colors.css:
--------------------------------------------------------------------------------
1 | @value blue: #0c77f8;
2 | @value red: #ff0000;
3 | @value green: #aaf200;
4 |
--------------------------------------------------------------------------------
/test/moduleTestCases/class-names/test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tangjinzhou/css-flat-loader/HEAD/test/moduleTestCases/class-names/test.png
--------------------------------------------------------------------------------
/test/moduleTestCases/class-names/expected.css:
--------------------------------------------------------------------------------
1 | ._class-1_, ._class-10_ ._bar-1_ {
2 | color: green;
3 | }
4 | .im{
5 | color: aliceblue;
6 | }
7 |
--------------------------------------------------------------------------------
/demo/css-flat.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | sourceMap: true,
3 | plugins: [
4 | require('precss')(),
5 | require('autoprefixer')(),
6 | ],
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/test/moduleTestCases/class-names/source.css:
--------------------------------------------------------------------------------
1 | .title{
2 | background: #ffffff url("http://img.alicdn.com/tps/TB1ld1GNFXXXXXLapXXXXXXXXXX-200-200.png");
3 | }
4 | :global(.test){
5 | color: red;
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/demo/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './components/App';
4 |
5 | ReactDOM.render(
6 | ,
7 | document.body.appendChild(document.createElement('div'))
8 | );
9 |
--------------------------------------------------------------------------------
/demo/components/Wrap.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import style from './Wrap.less';
3 |
4 | export default () => {
5 | return (
6 |
7 | Hello World
8 |
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 | CSS Flat Demo
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.{json,yml}]
12 | indent_size = 2
13 |
--------------------------------------------------------------------------------
/demo/components/Wrap.less:
--------------------------------------------------------------------------------
1 | .test {
2 | background-color: #cccccc;
3 | border: 1px solid #DDDDDD;
4 | font-size: 51px;
5 | }
6 | .title {
7 | display: flex;
8 | font-size: 50px;
9 | margin: 0 auto;
10 | border-bottom-left-radius: 20px;
11 | }
12 |
--------------------------------------------------------------------------------
/demo/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import style from './App.less';
3 | import Wrap from './Wrap';
4 | export default () => {
5 | return (
6 |
7 |
8 | Hello World
9 |
10 |
11 |
12 | );
13 | };
14 |
--------------------------------------------------------------------------------
/test/moduleMinimizeTestCases/keyframes-and-animation/expected.css:
--------------------------------------------------------------------------------
1 | @keyframes _bounce_{0%{transform:translateY(-100%);opacity:0}5%{transform:translateY(-50%);opacity:1}}@keyframes _bounce2_{0%{transform:translateY(-100%);opacity:0}50%{transform:translateY(-50%);opacity:1}}
2 |
3 | /* comment */._bounce_{animation-name:_bounce_;animation:_bounce2_ 1s ease;z-index:1442}
--------------------------------------------------------------------------------
/test/moduleMinimizeTestCases/keyframes-and-animation/source.css:
--------------------------------------------------------------------------------
1 | @keyframes bounce {
2 | 0% {
3 | transform: translateY(-100%);
4 | opacity: 0;
5 | }
6 | 5% {
7 | transform: translateY(-50%);
8 | opacity: 1;
9 | }
10 | }
11 |
12 | @keyframes bounce2 {
13 | 0% {
14 | transform: translateY(-100%);
15 | opacity: 0;
16 | }
17 | 50% {
18 | transform: translateY(-50%);
19 | opacity: 1;
20 | }
21 | }
22 |
23 | /* comment */
24 | .bounce {
25 | animation-name: bounce;
26 | animation: bounce2 1s ease;
27 | z-index: 1442;
28 | }
29 |
--------------------------------------------------------------------------------
/demo/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/bash
2 |
3 | # number of demos
4 | num=6
5 |
6 | mkdir -p dist
7 |
8 | count=1;
9 | while [ $count -le $num ]; do
10 | cp -rf demo0$count dist
11 | echo -e "cp demo0$count succeed"
12 | pushd dist/demo0$count
13 | ../../node_modules/.bin/webpack
14 | popd
15 | echo -e "build demo0$count succeed"
16 | echo "============="
17 | count=$((count + 1))
18 | done
19 |
20 | ./node_modules/.bin/gh-pages -d dist
21 | echo -e "commit to gh-pages branch succeed"
22 | rm -rf dist
23 | echo -e "delete dist directory succeed"
24 |
--------------------------------------------------------------------------------
/demo/components/App.less:
--------------------------------------------------------------------------------
1 | @import "common";
2 | @grey: #CCCCCC;
3 | :global(.title){
4 | color: red;
5 | }
6 | .title {
7 | color: black;
8 | color: red;
9 | border: #fff solid 1px;
10 | margin-top: 10px;
11 | margin-right: 20px;
12 | margin-bottom: 10px;
13 |
14 | }
15 | .title{
16 | margin-left: 20px;
17 | }
18 | .title:hover{
19 | color: @grey;
20 | }
21 | :global .main{
22 | color: transparent;
23 | }
24 | .test, .a{
25 | background-color: #DDDDDD;
26 |
27 | }
28 | @keyframes fadeOut {
29 | from {
30 | opacity: 1;
31 | }
32 | to {
33 | opacity: 0;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/test/moduleTest.js:
--------------------------------------------------------------------------------
1 | /*globals describe */
2 |
3 | var test = require("./helpers").testSingleItem;
4 |
5 | var path = require("path");
6 | var fs = require("fs");
7 | var testCasesPath = path.join(__dirname, "moduleTestCases");
8 | var testCases = fs.readdirSync(testCasesPath);
9 |
10 | describe("module", function() {
11 | testCases.forEach(function(name) {
12 | var source = fs.readFileSync(path.join(testCasesPath, name, "source.css"), "utf-8");
13 | var expected = fs.readFileSync(path.join(testCasesPath, name, "expected.css"), "utf-8");
14 |
15 | test(name, source, expected, "?module&sourceMap&localIdentName=_[local]_");
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/test/moduleMinimizeTest.js:
--------------------------------------------------------------------------------
1 | /*globals describe */
2 |
3 | var test = require("./helpers").testSingleItem;
4 |
5 | var path = require("path");
6 | var fs = require("fs");
7 | var testCasesPath = path.join(__dirname, "moduleMinimizeTestCases");
8 | var testCases = fs.readdirSync(testCasesPath);
9 |
10 | describe("module minimize", function() {
11 | testCases.forEach(function(name) {
12 | var source = fs.readFileSync(path.join(testCasesPath, name, "source.css"), "utf-8");
13 | var expected = fs.readFileSync(path.join(testCasesPath, name, "expected.css"), "utf-8");
14 |
15 | test(name, source, expected, '?' + JSON.stringify({
16 | module: true,
17 | sourceMap: true,
18 | minimize: {
19 | discardComments: false
20 | },
21 | localIdentName: '_[local]_'
22 | }));
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | branches:
4 | only:
5 | - master
6 | matrix:
7 | fast_finish: true
8 | include:
9 | - os: linux
10 | node_js: "7"
11 | env: WEBPACK_VERSION="2.2.0" JOB_PART=lint
12 | - os: linux
13 | node_js: "6"
14 | env: WEBPACK_VERSION="2.2.0" JOB_PART=test
15 | - os: linux
16 | node_js: "4.3"
17 | env: WEBPACK_VERSION="2.2.0" JOB_PART=test
18 | - os: linux
19 | node_js: "7"
20 | env: WEBPACK_VERSION="2.2.0" JOB_PART=test
21 | - os: linux
22 | node_js: "4.3"
23 | env: WEBPACK_VERSION="1.14.0" JOB_PART=test
24 | before_install:
25 | - nvm --version
26 | - node --version
27 | before_script:
28 | - 'if [ "$WEBPACK_VERSION" ]; then yarn add webpack@^$WEBPACK_VERSION; fi'
29 | script:
30 | - yarn run travis:$JOB_PART
31 | after_success:
32 | - bash <(curl -s https://codecov.io/bash)
33 |
--------------------------------------------------------------------------------
/src/declValueMap.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable quotes */
2 | const declValueMap = {
3 | "0": "0",
4 | "absolute": "a",
5 | "auto": "a1",
6 | "block": "b",
7 | "bold": "b1",
8 | "bolder": "b2",
9 | "both": "b3",
10 | "break-all": "b4",
11 | "center": "c",
12 | "collapse": "c1",
13 | "default": "d",
14 | "ellipsis": "e",
15 | "fixed": "f",
16 | "gray": "g",
17 | "hidden": "h",
18 | "infinite": "i",
19 | "inherit": "i1",
20 | "inline": "i2",
21 | "left": "l",
22 | "middle": "m",
23 | "no-repeat": "n",
24 | "none": "n1",
25 | "normal": "n2",
26 | "nowrap": "n3",
27 | "padding": "p",
28 | "pointer": "p1",
29 | "red": "r",
30 | "relative": "r1",
31 | "right": "r2",
32 | "solid": "s",
33 | "table": "t",
34 | "tahoma": "t1",
35 | "tomato": "t2",
36 | "top": "t3",
37 | "underline": "u",
38 | "visible": "v",
39 | "white": "w",
40 | }
41 | module.exports = declValueMap
42 |
--------------------------------------------------------------------------------
/src/error.js:
--------------------------------------------------------------------------------
1 | const formatCodeFrame = require('babel-code-frame')
2 |
3 | function formatMessage(message, loc, source) {
4 | let formatted = message
5 | if (loc) {
6 | formatted = formatted + ' (' + loc.line + ':' + loc.column + ')'
7 | }
8 | if (loc && source) {
9 | formatted = formatted + '\n\n' + formatCodeFrame(source, loc.line, loc.column) + '\n'
10 | }
11 | return formatted
12 | }
13 |
14 | function CSSFlatError(error) {
15 | Error.call(this)
16 | Error.captureStackTrace(this, CSSFlatError)
17 | this.name = 'Syntax Error'
18 | this.error = error.input.source
19 | const loc = error.line !== null && error.column !== null ? { line: error.line, column: error.column } : null
20 | this.message = formatMessage(error.reason, loc, error.input.source)
21 | this.hideStack = true
22 | }
23 |
24 | CSSFlatError.prototype = Object.create(Error.prototype)
25 | CSSFlatError.prototype.constructor = CSSFlatError
26 |
27 | module.exports = CSSFlatError
28 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "css-flat-demos",
3 | "version": "1.0.0",
4 | "description": "A collection of simple demos of CSS Flat",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-dev-server --config webpack.config.js"
8 | },
9 | "keywords": [
10 | "CSS",
11 | "CSS Flat"
12 | ],
13 | "dependencies": {
14 | "babel-core": "^6.9.1",
15 | "babel-loader": "^6.2.4",
16 | "babel-preset-es2015": "^6.9.0",
17 | "babel-preset-react": "^6.5.0",
18 | "babel-preset-stage-0": "^6.5.0",
19 | "css-loader": "^0.23.1",
20 | "cssnano": "^3.10.0",
21 | "less": "^2.7.2",
22 | "less-loader": "^4.0.3",
23 | "optimize-css-assets-webpack-plugin": "^1.3.1",
24 | "postcss-loader": "^0.9.1",
25 | "postcss-modules-values": "^1.1.3",
26 | "react": "^15.1.0",
27 | "react-dom": "^15.1.0",
28 | "style-loader": "^0.13.1",
29 | "webpack": "^1.13.1",
30 | "webpack-dev-server": "^1.14.1"
31 | },
32 | "devDependencies": {
33 | "css-flat-loader": "latest",
34 | "extract-text-webpack-plugin": "^1.0.1",
35 | "gh-pages": "^0.11.0"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/pseudoMap.js:
--------------------------------------------------------------------------------
1 | function getPseudoShort(preShort, pseudo) {
2 | return preShort + '_' + pseudo.split('(')[1].split(')')[0]
3 | }
4 | const pseudoMap = {
5 | 'link': 'l',
6 | 'visited': 'v',
7 | 'active': 'a',
8 | 'hover': 'h',
9 | 'focus': 'f',
10 | 'first-letter': 'fle',
11 | 'first-line': 'fli',
12 | 'first-child': 'fc',
13 | 'before': 'be',
14 | 'after': 'af',
15 | 'first-of-type': 'fot',
16 | 'last-of-type': 'lot',
17 | 'only-of-type': 'oot',
18 | 'only-child': 'oc',
19 | 'nth-child': (pseudo) => {
20 | return getPseudoShort('nc', pseudo)
21 | },
22 | 'nth-last-child': (pseudo) => {
23 | return getPseudoShort('nlc', pseudo)
24 | },
25 | 'nth-of-type': (pseudo) => {
26 | return getPseudoShort('not', pseudo)
27 | },
28 | 'nth-last-of-type': (pseudo) => {
29 | return getPseudoShort('nlot', pseudo)
30 | },
31 | 'last-child': 'lc',
32 | 'root': 'r',
33 | 'empty': 'e',
34 | 'target': 't',
35 | 'enabled': 'en',
36 | 'disabled': 'd',
37 | 'checked': 'c',
38 | }
39 | module.exports = pseudoMap
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright JS Foundation and other contributors
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | 'Software'), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/demo/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var ExtractTextPlugin = require('extract-text-webpack-plugin');
3 | var OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
4 |
5 | module.exports = {
6 | entry: __dirname + '/index.js',
7 | output: {
8 | publicPath: '/',
9 | filename: './bundle.js'
10 | },
11 | module: {
12 | loaders: [
13 | {
14 | test: /\.jsx?$/,
15 | exclude: /node_modules/,
16 | loader: 'babel',
17 | query: {
18 | presets: ['es2015', 'stage-0', 'react']
19 | }
20 | },
21 | {
22 | test: /\.less$/,
23 | loader: "style-loader!css-flat-loader!css-loader?modules&localIdentName=[path][name]---[local]---[hash:base64:5]&sourceMap!less-loader?sourceMap",
24 | //loader: ExtractTextPlugin.extract("css-flat!css?modules&localIdentName=_[local]_&sourceMap!less?sourceMap"),
25 | },
26 | ]
27 | },
28 | plugins: [
29 | new ExtractTextPlugin("[name].css", {
30 | allChunks: true,
31 | }),
32 | new OptimizeCssAssetsPlugin(),
33 | ]
34 | };
35 |
--------------------------------------------------------------------------------
/src/getLoaderConfig.js:
--------------------------------------------------------------------------------
1 | const resolve = require('path').resolve
2 |
3 | const config = require('cosmiconfig')
4 | const assign = require('lodash').assign
5 |
6 | const loadPlugins = require('postcss-load-plugins/lib/plugins.js')
7 |
8 | module.exports = function cssFlatConfig(ctx, path, options) {
9 | ctx = assign({ cwd: process.cwd(), env: process.env.NODE_ENV }, ctx)
10 | path = path ? resolve(path) : process.cwd()
11 | options = assign({ rcExtensions: true }, options)
12 | if (!ctx.env) process.env.NODE_ENV = 'development'
13 | let file
14 | return config('css-flat', options)
15 | .load(path)
16 | .then(function (result) {
17 | if (!result) throw Error('No css-flat Config found in: ' + path)
18 |
19 | file = result ? result.filepath : ''
20 |
21 | return result ? result.config : {}
22 | })
23 | .then((params) => {
24 | if (typeof params === 'function') params = params(ctx)
25 | else params = assign(params, ctx)
26 |
27 | if (!params.plugins) params.plugins = []
28 |
29 | return assign({}, params, {
30 | plugins: loadPlugins(params),
31 | file,
32 | })
33 | })
34 | }
35 |
--------------------------------------------------------------------------------
/src/getSelectorType.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash')
2 | const pseudoRegex = /:[^\s]*/
3 | const attributeRegex = /\[[^\s]*/
4 | module.exports = function getSelectorType(selector, localsMap) {
5 | let isGlobal = true
6 | let isClassSelector = true
7 | let type = 'normal'
8 | let selectorHalf = ''
9 | const tempSel = _.trim(selector).replace(/ |>|\+/g, ' ').replace(/\.|#/g, '')
10 | const names = tempSel.split(' ')
11 | names.forEach((name) => {
12 | if (localsMap[name.replace(/\[|:/, ' ').split(' ')[0]]) {
13 | isGlobal = false
14 | }
15 | })
16 | if (!isGlobal && (names.length !== 1 || _.trim(selector)[0] !== '.')) {
17 | isClassSelector = false
18 | throw new Error('css flat仅允许单层类选择器:' + selector)
19 | } else {
20 | const s = _.trim(selector)
21 | const pseudoM = s.match(pseudoRegex)
22 | const attributeM = s.match(attributeRegex)
23 | if (pseudoM) {
24 | type = 'pseudo'
25 | selectorHalf = pseudoM[0]
26 | } else if (attributeM) {
27 | type = 'attribute'
28 | selectorHalf = attributeM[0]
29 | }
30 | }
31 | return {
32 | isGlobal,
33 | isClassSelector: !isGlobal && isClassSelector,
34 | type,
35 | selectorHalf,
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/getSelectorName.js:
--------------------------------------------------------------------------------
1 | const pseudoMapDefault = require('./pseudoMap')
2 | const declPropMapDefault = require('./declPropMap')
3 | const declValueMapDefault = require('./declValueMap')
4 | let parentNum = 1
5 | let declPropId = 1
6 | let declValueId = 1
7 | let pseudoId = 1
8 | const parentParamsSuffixs = {}
9 | module.exports = function getSelectorName(decl, opt = {}) {
10 | const {
11 | prefix = '',
12 | parentParams = '',
13 | parentName,
14 | atRulesConfig,
15 | selectorHalf,
16 | pseudoMap = pseudoMapDefault,
17 | declPropMap = declPropMapDefault,
18 | declValueMap = declValueMapDefault,
19 | } = opt
20 | const { value: declValue, prop: declProp } = decl
21 | const name = []
22 | const name1 = []
23 | let declPropName = ''
24 | let declValueName = ''
25 | let pseudoName = ''
26 | declPropName = declPropMap[declProp] || declPropId++
27 | declPropMap[declProp] = declPropName
28 | name1.push(declPropName)
29 | if (selectorHalf !== '') {
30 | pseudoName = pseudoMap[selectorHalf.slice(1).split('(')[0]]
31 | if (typeof pseudoName === 'function') {
32 | pseudoName = pseudoName(selectorHalf.slice(1))
33 | } else if (pseudoName === undefined) {
34 | pseudoName = pseudoMap[selectorHalf] || pseudoId++
35 | pseudoMap[selectorHalf] = pseudoName
36 | }
37 | }
38 |
39 | if (parentParams !== 'normal') {
40 | const atRulesConfigKey = ('@' + parentName + parentParams).replace(/ /g, '')
41 | const atRuleSuffix = (atRulesConfig[atRulesConfigKey] || {}).suffix
42 | parentParamsSuffixs[parentParams] = parentParamsSuffixs[parentParams] || atRuleSuffix || parentNum++
43 | }
44 |
45 | if (parentParamsSuffixs[parentParams]) {
46 | name1.push(pseudoName, parentParamsSuffixs[parentParams])
47 | } else if (pseudoName) {
48 | name1.push(pseudoName)
49 | }
50 |
51 | declValueName = declValueMap[declValue] || declValueId++
52 | declValueMap[declValue] = declValueName
53 |
54 | name.push(prefix, name1.join('_'), declValueName)
55 | return name.join('-')
56 | }
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "css-flat-loader",
3 | "version": "0.2.1",
4 | "author": "tangjinzhou",
5 | "description": "css flat loader module for webpack",
6 | "engines": {
7 | "node": ">=0.12.0 || >=4.3.0 <5.0.0 || >=5.10"
8 | },
9 | "files": [
10 | "index.js",
11 | "src"
12 | ],
13 | "dependencies": {
14 | "autoprefixer": "^7.1.0",
15 | "babel-code-frame": "^6.11.0",
16 | "cosmiconfig": "^2.1.3",
17 | "css-loader": "^0.28.0",
18 | "css-selector-tokenizer": "^0.7.0",
19 | "cssnano": "^3.10.0",
20 | "eslint-config-postcss": "^2.0.2",
21 | "loader-utils": "^1.0.2",
22 | "lodash": "^4.17.4",
23 | "object-assign": "^4.1.1",
24 | "postcss": "^5.0.6",
25 | "postcss-load-plugins": "^2.3.0",
26 | "postcss-modules-extract-imports": "^1.0.0",
27 | "postcss-modules-local-by-default": "^1.0.1",
28 | "postcss-modules-scope": "^1.0.0",
29 | "postcss-modules-values": "^1.1.0",
30 | "postcss-value-parser": "^3.3.0",
31 | "precss": "^1.4.0",
32 | "source-list-map": "^0.1.7"
33 | },
34 | "devDependencies": {
35 | "codecov": "^1.0.1",
36 | "eslint": "3.14.0",
37 | "istanbul": "^0.4.5",
38 | "mocha": "^3.2.0",
39 | "should": "^11.1.2",
40 | "standard-version": "^4.0.0"
41 | },
42 | "scripts": {
43 | "test": "mocha",
44 | "test:cover": "npm run cover -- --report lcovonly",
45 | "lint": "eslint lib test",
46 | "travis:test": "npm run cover",
47 | "travis:lint": "npm run lint",
48 | "cover": "istanbul cover node_modules/mocha/bin/_mocha",
49 | "release": "yarn run standard-version"
50 | },
51 | "repository": {
52 | "type": "git",
53 | "url": "git@github.com:tangjinzhou/css-flat-loader"
54 | },
55 | "eslintConfig": {
56 | "extends": "eslint-config-postcss/es5",
57 | "env": {
58 | "jest": true
59 | },
60 | "rules": {
61 | "comma-dangle": [
62 | 2,
63 | "always-multiline"
64 | ],
65 | "semi": [
66 | 2,
67 | "never"
68 | ],
69 | "no-trailing-spaces": "off",
70 | "max-len": [
71 | 2,
72 | 180
73 | ]
74 | }
75 | },
76 | "license": "MIT"
77 | }
78 |
--------------------------------------------------------------------------------
/src/declPropMap.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable quotes */
2 | const declProsMap = {
3 | "animation": "a",
4 | "animation-iteration-count": "a1",
5 | "background": "b",
6 | "background-clip": "b1",
7 | "background-color": "b2",
8 | "background-image": "b3",
9 | "background-position": "b4",
10 | "background-repeat": "b5",
11 | "background-size": "b6",
12 | "border": "b7",
13 | "border-bottom": "b8",
14 | "border-bottom-color": "b9",
15 | "border-collapse": "b10",
16 | "border-color": "b11",
17 | "border-left": "b12",
18 | "border-left-color": "b13",
19 | "border-radius": "b14",
20 | "border-right": "b15",
21 | "border-right-color": "b16",
22 | "border-spacing": "b17",
23 | "border-style": "b18",
24 | "border-top": "b19",
25 | "border-top-color": "b20",
26 | "border-top-left-radius": "b21",
27 | "border-top-right-radius": "b22",
28 | "border-width": "b23",
29 | "bottom": "b24",
30 | "box-shadow": "b25",
31 | "clear": "c",
32 | "color": "c1",
33 | "content": "c2",
34 | "cursor": "c3",
35 | "display": "d",
36 | "filter": "f",
37 | "float": "f1",
38 | "font": "f2",
39 | "font-family": "f3",
40 | "font-size": "f4",
41 | "font-style": "f5",
42 | "font-weight": "f6",
43 | "height": "h",
44 | "left": "l",
45 | "line-height": "l1",
46 | "list-style": "l2",
47 | "margin": "m",
48 | "margin-bottom": "m1",
49 | "margin-left": "m2",
50 | "margin-right": "m3",
51 | "margin-top": "m4",
52 | "max-height": "m5",
53 | "max-width": "m6",
54 | "min-height": "m7",
55 | "opacity": "o",
56 | "outline": "o1",
57 | "overflow": "o2",
58 | "overflow-x": "o3",
59 | "overflow-y": "o4",
60 | "padding": "p",
61 | "padding-bottom": "p1",
62 | "padding-left": "p2",
63 | "padding-right": "p3",
64 | "padding-top": "p4",
65 | "position": "p5",
66 | "right": "r",
67 | "text-align": "t",
68 | "text-decoration": "t1",
69 | "text-indent": "t2",
70 | "text-overflow": "t3",
71 | "text-shadow": "t4",
72 | "top": "t5",
73 | "transform": "t6",
74 | "transition": "t7",
75 | "vertical-align": "v",
76 | "visibility": "v1",
77 | "white-space": "w",
78 | "width": "w1",
79 | "word-break": "w2",
80 | "z-index": "z",
81 | "zoom": "z1",
82 | }
83 | module.exports = declProsMap
84 |
--------------------------------------------------------------------------------
/test/helpers.js:
--------------------------------------------------------------------------------
1 | require("should");
2 | var cssLoader = require("css-loader/index.js");
3 | var vm = require("vm");
4 | var flatLoader = require('../src/loader');
5 | var clone = require('lodash/clone')
6 |
7 | function getEvaluated(output, modules) {
8 | try {
9 | var fn = vm.runInThisContext("(function(module, exports, require) {" + output + "})", "testcase.js");
10 | var m = {exports: {}, id: 1};
11 | fn(m, m.exports, function (module) {
12 | if (module.indexOf("css-base") >= 0)
13 | return require("css-loader/lib/css-base");
14 | if (module.indexOf("-!/path/css-loader!") === 0)
15 | module = module.substr(19);
16 | if (modules && modules[module])
17 | return modules[module];
18 | return "{" + module + "}";
19 | });
20 | } catch (e) {
21 | console.error(output); // eslint-disable-line no-console
22 | throw e;
23 | }
24 | delete m.exports.toString;
25 | delete m.exports.i;
26 | return m.exports;
27 | }
28 |
29 | function runLoader(loader, input, map, addOptions, callback) {
30 | var tempCallback = function (err, output) {
31 | callback(err, output)
32 | flatLoader.call({
33 | options: {
34 | context: ""
35 | },
36 | callback: function () {
37 | },
38 | async: function (res) {
39 | return () => {
40 | }
41 | },
42 | loaders: [{request: "/path/css-loader"}],
43 | loaderIndex: 0,
44 | context: "",
45 | resource: "test.css",
46 | resourcePath: "test.css",
47 | request: "css-loader!test.css",
48 | emitError: function (message) {
49 | throw new Error(message);
50 | }
51 | }, clone(output), map);
52 | }
53 | var opt = {
54 | options: {
55 | context: ""
56 | },
57 | callback: tempCallback,
58 | async: function () {
59 | return tempCallback;
60 | },
61 | loaders: [{request: "/path/css-loader"}],
62 | loaderIndex: 0,
63 | context: "",
64 | resource: "test.css",
65 | resourcePath: "test.css",
66 | request: "css-loader!test.css",
67 | emitError: function (message) {
68 | throw new Error(message);
69 | }
70 | };
71 | Object.keys(addOptions).forEach(function (key) {
72 | opt[key] = addOptions[key];
73 | });
74 | loader.call(opt, input, map);
75 | }
76 |
77 | exports.testSingleItem = function testSingleItem(name, input, result, query, modules) {
78 | it(name, function (done) {
79 | runLoader(cssLoader, input, undefined, {
80 | query: query
81 | }, function (err, output) {
82 |
83 | if (err) return done(err);
84 | var exports = getEvaluated(output, modules);
85 | /*Array.isArray(exports).should.be.eql(true);
86 | (exports.length).should.be.eql(1);
87 | (exports[0].length >= 3).should.be.eql(true);
88 | (exports[0][0]).should.be.eql(1);
89 | (exports[0][2]).should.be.eql("");
90 | (exports[0][1]).should.be.eql(result);*/
91 | done();
92 | });
93 | });
94 | };
95 |
96 |
--------------------------------------------------------------------------------
/src/loader.js:
--------------------------------------------------------------------------------
1 | const loaderUtils = require('loader-utils')
2 | const processCss = require('./processCss')
3 | const CSSFlatError = require('./error')
4 | const vm = require('vm')
5 | const path = require('path')
6 | const loadConfig = require('./getLoaderConfig')
7 | const urlItemRegExpG = /___CSS_FLAT_LOADER_URL___([0-9]+)___/g
8 | const urlItemRegExp = /___CSS_FLAT_LOADER_URL___([0-9]+)___/
9 | const urlItems = []
10 | function getEvaluated(output, modules) {
11 | const m = { exports: {} }
12 | try {
13 | const fn = vm.runInThisContext('(function(module, exports, require) {' + output + '})', 'css-loader-output.js')
14 | fn(m, m.exports, function (module) {
15 | if (module.indexOf('css-base') >= 0)
16 | return require('css-loader/lib/css-base')
17 | if (module.indexOf('-!/path/css-loader!') === 0)
18 | module = module.substr(19)
19 | if (modules && modules[module])
20 | return modules[module]
21 | const loaderUrl = '___CSS_FLAT_LOADER_URL___' + urlItems.length + '___'
22 | urlItems.push({ url: module })
23 | return loaderUrl
24 | })
25 | } catch (e) {
26 | throw e
27 | }
28 | delete m.exports.toString
29 | delete m.exports.i
30 | return m.exports
31 | }
32 |
33 | module.exports = function (input) {
34 | if (this.cacheable) this.cacheable()
35 | const callback = this.async()
36 | const loader = this
37 | const file = this.resourcePath
38 | const params = loaderUtils.getOptions(this) || {}
39 | params.plugins = params.plugins || this.options['css-flat']
40 |
41 | let configPath
42 |
43 | // params.plugins = []
44 | // params.sourceMap = true
45 |
46 | if (params.config) {
47 | if (path.isAbsolute(params.config)) {
48 | configPath = params.config
49 | } else {
50 | configPath = path.join(process.cwd(), params.config)
51 | }
52 | } else {
53 | configPath = path.dirname(file)
54 | }
55 | const exports = getEvaluated(input)
56 | Promise.resolve().then(function () {
57 | if ( typeof params.plugins !== 'undefined' ) {
58 | return params
59 | } else {
60 | return loadConfig({ webpack: loader }, configPath, { argv: false })
61 | }
62 | }).then(function (config) {
63 | let inputMap = null
64 | if (config.sourceMap && exports[0][3]) {
65 | inputMap = JSON.stringify(exports[0][3])
66 | }
67 |
68 | processCss(exports[0][1], inputMap, {
69 | from: loaderUtils.getRemainingRequest(loader),
70 | to: loaderUtils.getCurrentRequest(loader),
71 | params: config,
72 | loaderContext: loader,
73 | locals: exports.locals,
74 | }, (err, result) => {
75 | if (err) return callback(err)
76 |
77 | let cssAsString = JSON.stringify(result.source)
78 | cssAsString = cssAsString.replace(urlItemRegExpG, (item) => {
79 | const match = urlItemRegExp.exec(item)
80 | const idx = +match[1]
81 | const urlItem = urlItems[idx]
82 | const urlRequest = urlItem.url
83 | return '\" + require(' + loaderUtils.stringifyRequest(this, urlRequest) + ') + \"'
84 | })
85 | let exportJs = JSON.stringify(result.exports)
86 | if (exportJs) {
87 | exportJs = 'exports.locals = ' + exportJs + ';'
88 | }
89 |
90 | let moduleJs
91 | if (config.sourceMap && result.map) {
92 | let map = result.map
93 | map = JSON.stringify(map)
94 | moduleJs = 'exports.push([module.id, ' + cssAsString + ', "", ' + map + ']);'
95 | } else {
96 | moduleJs = 'exports.push([module.id, ' + cssAsString + ', ""]);'
97 | }
98 |
99 | return callback(null, 'exports = module.exports = require(' +
100 | loaderUtils.stringifyRequest(loader, require.resolve('css-loader/lib/css-base')) +
101 | ')(' + params.sourceMap + ');\n' +
102 | '// imports\n' +
103 | '' + '\n\n' +
104 | '// module\n' +
105 | moduleJs + '\n\n' +
106 | '// exports\n' +
107 | exportJs)
108 | })
109 | }).catch((err) => {
110 | console.log(err)
111 | if (err.name === 'CssSyntaxError') {
112 | callback(new CSSFlatError(err))
113 | } else {
114 | callback(err)
115 | }
116 | })
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # css-flat-loader
2 |
3 | ## CSS Flat
4 |
5 | CSS Flat(CSS 扁平化)是一种模块化解决方案,基于Post CSS生态开发。
6 |
7 | 主要解决问题:
8 | 1. 达到模块化CSS的能力
9 | 2. 解决由于业务的持续迭代,导致的CSS样式文件的线性增长问题(CSS Modules尤其明显)
10 |
11 | CSS Flat 将CSS样式格式化为单条样式,开发时只需要按照正常文件书写:
12 |
13 | ```css
14 | .className {
15 | display: block;
16 | color: red;
17 | margin: 0 auto;
18 | }
19 | .className:hover {
20 | color: green;
21 | magin-top: 10px;
22 | }
23 | ```
24 | Flat化之后:
25 | ```css
26 | .a-d-b {
27 | display: block;
28 | }
29 | .a-c-1 {
30 | color: red;
31 | }
32 | .a-m-2 {
33 | margin: 0 auto;
34 | }
35 | .a-c_h-3:hover {
36 | color: green;
37 | }
38 | .css-flat .a-mt_h-4:hover {
39 | margin-top: 10px;
40 | }
41 | ```
42 | 当你在js文件中 import CSS Flat文件时,会export一个对象,该对象包含Flat化之后
43 | 的信息(className: newClassNames):
44 |
45 | ```js
46 | import styles from "./style.css"; // { classNames: 'a-d-b a-c-1 a-m-2 a-c_h-3 a-mt_h-4 '}
47 | // import { className } from "./style.css";
48 |
49 | element.innerHTML = '';
50 | ```
51 |
52 | ### 原则
53 |
54 | 1. 仅处理单层类选择器
55 |
56 | ```css
57 | /* 不支持 */
58 | .main.home-main {}
59 | .main .title {}
60 | .main span {}
61 | #main {}
62 | ```
63 | 2. 简写权重小于非简写权重
64 | 3. 媒体查询权重大于普通样式,不同条件的媒体查询权重需自行配置
65 |
66 | ### 使用方法
67 | ```bash
68 | npm install --save-dev css-flat-loader
69 | ```
70 | 目前依赖在CSS Modules的基础上来判断是否需要Flat话,后续会独立出来,详见demo
71 | ```js
72 | {
73 | test: /\.less$/,
74 | // loader: "style-loader!css-flat-loader!css-loader?modules!less-loader",
75 | loader: ExtractTextPlugin.extract("css-flat-loader!css?modules&localIdentName=_[local]_!less")
76 | },
77 | ```
78 |
79 | ### stylelint设置
80 | 建议添加如下设置来对样式文件进行检测:
81 |
82 | declaration-block-no-shorthand-property-overrides: true
83 | [查看](https://stylelint.io/user-guide/rules/declaration-block-no-shorthand-property-overrides/)
84 |
85 | max-nesting-depth: [0, ignore: ["blockless-at-rules"]]
86 | [查看](https://stylelint.io/user-guide/rules/max-nesting-depth/)
87 |
88 | selector-max-class: 1
89 | [查看](https://stylelint.io/user-guide/rules/selector-max-class/)
90 |
91 | selector-max-id: 0
92 | [查看](https://stylelint.io/user-guide/rules/selector-max-id/)
93 |
94 | ### API
95 |
96 | ```js
97 | // 配置文件css-flat.config.js
98 | module.exports = {
99 | plugins: [
100 | require('precss')(),
101 | require('autoprefixer')(),
102 | ]
103 | }
104 | ```
105 | 注:对于px2rem, autoprefixer等推荐在css-flat.config.js的plugins中配置
106 |
107 | flat后的样式公式如下:
108 |
109 | ```text
110 | .htmlClass*n .prefix-declProp(_(pseudo)(_atRule))-declValue {}
111 | ```
112 | 1. htmlClass 根节点类名,用来增加权重,如margin-top的权重大于margin,n为'-'的个数
113 | 2. 当atRule存在,但无伪类时,pseudo为空字符,但下划线(_)保留,避免冲突
114 | 3. 当提供的map映射无相关属性时,脚本会自动从1自增分配,所以如需自定义提供map,不要提供数字,以免冲突
115 |
116 | | 属性 | 类型 | 默认值 | 描述 |
117 | | ---------- | --- | --- | --- |
118 | |**`htmlClass`**|`{string}`|`'css-flat'`|根节点类名,请自行在html标签上添加|
119 | |**`prefix`**|`{string}`|`'a'`|类名前缀|
120 | |**`declPropMap`**|`{Object}`|见[属性映射](https://github.com/tangjinzhou/css-flat-loader/blob/master/src/declPropMap.js)|属性映射|
121 | |**`pseudoMap`**|`{Object}`|见[伪类映射](https://github.com/tangjinzhou/css-flat-loader/blob/master/src/pseudoMap.js)|伪类映射|
122 | |**`atRules`**|`{Array}`|`[]`|@规则的映射,如@media等,数组顺序代表权重|
123 | |**`declValueMap`**|`{Object}`|见[值映射](https://github.com/tangjinzhou/css-flat-loader/blob/master/src/declValueMap.js)|值映射|
124 | |**`plugins`**|`{Array}`|`[]`|插件|
125 | |**`sourceMap`**|`{Bool}`|`false`|sourceMap为true时,会保留css modules hash之后的类,但属性会改变成`--sourceMap-xxx`, 间接达到sourceMap的功能|
126 |
127 | ### 更多
128 | 对于一些大型webview APP可按照规则容器内置通用common.css, 上线时做一次diff,仅需线上加载common.css不包含的CSS,
129 | 进一步降低样式文件,提升加载速度。
130 |
131 | ## 疑惑
132 | 1. css文件减少,那html或js文件增大了
133 | 答:没错,这是肯定的,不过不知你是否注意到,当你打开浏览器控制台查看元素,发现在元素上生效的样式并没有那么多条,更多的是层层覆盖。
134 | 针对该问题做了如下优化:
135 | 1. 扁平化之前会使用cssnano进行合并
136 | 2. 扁平化之后处理prefixer
137 | 3. 规则尽可能简短
138 | 4. 经过扁平化处理后注入到js中的也仅仅是一个对象,标签上也只是个对象值的引用
139 |
140 | 2. DOM节点的操作
141 | 答:经过处理后的类名是不具有可读性的,如果你使用的是react,那么也没必要操作dom,类名的可读性不再那么重要。如果你依然有操作dom的需求,你可以在模板中自行写类名。
142 |
143 | 3. sourceMap
144 | 答:这块的确不是特别好搞,目前的方案是开启sourceMap后,会保留css-modules hash后的类名,并将该类名下的样式自定义化使其不生效(如:--sourceMap-color: red),用于定位当前节点样式源文件的位置,基本可以满足需求。
145 |
146 | 4. 简写和非简写的处理
147 | 答:优先使用cssnona进行合并,合并不了的遵循以下规则:非简写权重大于简写
148 | 如margin-top 会被处理成 .css-flat .xxx {margin-top: ...}
149 | border-top-left-radius 会被处理成 .css-flat.css-flat.css-falt .xxx {margin-top: ...}
150 | 所以你需要在html根节点上手动添加css-flat类名(可自定义)
151 | 对于媒体查询规则类似,只是权重可自定义,更加灵活
152 |
153 | 5. 不支持嵌套,只支持类
154 | 答:对,该方案的核心思想就是扁平化处理css,对于嵌套我们不推荐,也不支持,如果你的项目已经组件化处理,单个组件的模板不会太大,也不应该太大,非嵌套的类方式不管从项目的后期可维护性上,还是浏览器的渲染上,都是利大于弊的。
155 |
156 | 6. 样式合并 如style.button + style.disabled
157 | 答:推荐使用less如下方式:
158 | ```css
159 | .button{
160 | }
161 | .button-diabled{
162 | .button()
163 | }
164 | ```
165 |
166 | 因扁平化处理后的样式顺序改变不应该影响最终的渲染结果,所以单个html标签上的类名不应该出现相同属性的样式,直接使用style.button + style.disabled的方式是不安全的。
167 | 细心的朋友可能已经注意到我们的规则.prefix-declProp(_(pseudo)(_atRule))-declValue {} 既有-又有_ 这两种分隔符,这里就是预留给处理相同属性的,你可以自己处理declProp(_(pseudo)(_atRule))相同的部分。
168 |
169 | ### 存在的问题
170 |
171 | 因开发中要支持样式文件的按需加载及热更新,无法过滤浏览器中已加载的样式,会很多重复的样式,在控制台中看着很不爽。上线时还需自行添加plugin处理重复样式。
172 |
173 |
174 |
175 |
176 |
--------------------------------------------------------------------------------
/src/processCss.js:
--------------------------------------------------------------------------------
1 | const CSSFlatError = require('./error')
2 | const postcss = require('postcss')
3 | const _ = require('lodash')
4 | const cssnano = require('cssnano')
5 | const getSelectorName = require('./getSelectorName')
6 | const getSelectorType = require('./getSelectorType')
7 |
8 | const cacheLocalRuleInfo = {}
9 | const parserPlugin = postcss.plugin('postcss-flat', (options) => {
10 | const {
11 | locals = {},
12 | prefix = 'a',
13 | atRulesConfig,
14 | htmlClass = 'css-flat',
15 | pseudoMap,
16 | declPropMap,
17 | declValueMap,
18 | sourceMap,
19 | inputMap,
20 | } = options
21 | const genMap = sourceMap && inputMap
22 | const localsMap = _.invert(locals)
23 | const localRuleMark = {}
24 | return (css) => {
25 | const exports = {}
26 | // const globalRule = []
27 | css.walkRules((rule) => {
28 | let parentParams = 'normal'
29 | let parentName = ''
30 | if (rule.parent.type === 'atrule') {
31 | parentName = rule.parent.name
32 | if (parentName === 'supports' || parentName === 'media') {
33 | parentParams = rule.parent.params
34 | } else {
35 | return
36 | }
37 | }
38 |
39 | rule.selector.split(',').forEach((sel) => {
40 | const selectorType = getSelectorType(sel, localsMap)
41 | const { isGlobal, isClassSelector, selectorHalf = '' } = selectorType
42 | if (isGlobal) {
43 | const globalSel = _.trim(sel)
44 | const cloneRule = rule.clone()
45 | cloneRule.selector = globalSel
46 | // globalRule.push(cloneRule)
47 | } else if (isClassSelector) {
48 | const className = sel.replace(/\.| /g, '').replace(selectorHalf, '')
49 | rule.walkDecls(function (decl) {
50 | const prop = decl.prop.replace('--sourceMap-', '')
51 | const value = decl.value
52 | const newClassName = getSelectorName(decl, {
53 | parentName,
54 | parentParams,
55 | prefix,
56 | atRulesConfig,
57 | selectorHalf,
58 | pseudoMap,
59 | declPropMap,
60 | declValueMap,
61 | })
62 | if (!cacheLocalRuleInfo[newClassName]) {
63 | let propLen = 0
64 | let priority = ''
65 | if (prop[0] !== '-') {
66 | propLen = prop.split('-').length
67 | }
68 | for (let i = 1; i < propLen; i++) {
69 | priority += '.' + htmlClass
70 | }
71 | cacheLocalRuleInfo[newClassName] = {
72 | prop,
73 | value,
74 | newClassName,
75 | selectorHalf, // 伪类后缀
76 | priority: priority + ' ',
77 | parentParams,
78 | }
79 | }
80 | localRuleMark[parentParams] = localRuleMark[parentParams] || {}
81 | localRuleMark[parentParams][newClassName] = cacheLocalRuleInfo[newClassName]
82 |
83 | const localsKey = localsMap[className]
84 | exports[localsKey] = (exports[localsKey] || (genMap ? className : '')) + ' ' + newClassName
85 | if (genMap) {
86 | decl.prop = '--sourceMap-' + prop
87 | decl.value = value
88 | }
89 | })
90 | }
91 | if (!genMap && !isGlobal) {
92 | rule.remove()
93 | }
94 | })
95 |
96 | })
97 | css.walkAtRules(/media|supports/, rule => {
98 | const atRulesConfigKey = ('@' + rule.name + rule.params).replace(/ /g, '')
99 | for (let newClassName in localRuleMark[rule.params]) {
100 | const { selectorHalf = '', priority: tempP, prop, value } = cacheLocalRuleInfo[newClassName]
101 | const atRulePriority = (atRulesConfig[atRulesConfigKey] || {}).priority || ''
102 | const priority = _.trim(atRulePriority + tempP) + ' '
103 | rule.append(priority + '.' + newClassName + selectorHalf + '{' + prop + ':' + value + '}')
104 | }
105 | })
106 |
107 | for (let newClassName in localRuleMark.normal) {
108 | const { selectorHalf = '', priority, prop, value } = cacheLocalRuleInfo[newClassName]
109 | const newSelector = priority + '.' + newClassName + selectorHalf
110 | css.append(newSelector + '{' + prop + ':' + value + '}')
111 | }
112 | options.exports = exports
113 | }
114 | })
115 |
116 | module.exports = function processCss(inputSource, inputMap, options, callback) {
117 | const {
118 | minimize,
119 | atRules = [],
120 | htmlClass = 'css-flat',
121 | plugins = [],
122 | sourceMap,
123 | } = options.params || {}
124 |
125 | const atRulesConfig = {}
126 | atRules.forEach((atRule, i) => {
127 | for (let key in atRule) {
128 | const value = atRule[key]
129 | atRulesConfig[key.replace(/ /g, '')] = {
130 | suffix: value,
131 | priority: Array(i + 1).fill('.' + htmlClass).join(''),
132 | }
133 | }
134 |
135 | })
136 |
137 | const parserOptions = _.assign({}, options.params, {
138 | atRulesConfig,
139 | locals: options.locals || {},
140 | inputMap,
141 | })
142 |
143 | const pipeline = postcss([
144 | cssnano({
145 | zindex: false,
146 | normalizeUrl: false,
147 | discardUnused: false,
148 | mergeIdents: false,
149 | autoprefixer: false,
150 | reduceTransforms: false,
151 | }),
152 | parserPlugin(parserOptions),
153 | ].concat(plugins))
154 |
155 | if (minimize) {
156 | const minimizeOptions = _.assign({}, minimize)
157 | ;['zindex', 'normalizeUrl', 'discardUnused', 'mergeIdents', 'reduceIdents', 'autoprefixer'].forEach((name) => {
158 | if (typeof minimizeOptions[name] === 'undefined')
159 | minimizeOptions[name] = false
160 | })
161 | pipeline.use(cssnano(minimizeOptions))
162 | }
163 |
164 | pipeline.process(inputSource, {
165 | from: '/css-flat-loader!' + options.from,
166 | to: options.to,
167 | map: sourceMap ? {
168 | prev: inputMap,
169 | sourcesContent: true,
170 | inline: false,
171 | annotation: false,
172 | } : null,
173 | }).then(function (result) {
174 | callback(null, {
175 | source: result.css,
176 | map: result.map && result.map.toJSON(),
177 | exports: parserOptions.exports,
178 | })
179 | }).catch((err) => {
180 | console.log(err)
181 | if (err.name === 'CssSyntaxError') {
182 | callback(new CSSFlatError(err))
183 | } else {
184 | callback(err)
185 | }
186 | })
187 | }
188 |
189 |
190 |
--------------------------------------------------------------------------------