Code Monkey home page Code Monkey logo

css-tools's Introduction

@adobe/css-tools

This is a fork of the npm css package due to low maintenance

CSS parser / stringifier.

Installation

$ npm install @adobe/css-tools

Usage

import { parse, stringify } from '@adobe/css-tools'
let obj = parse('body { font-size: 12px; }', options);
let css = stringify(obj, options);

API

parse(code, [options])

Accepts a CSS string and returns an AST object.

options:

  • silent: silently fail on parse errors.
  • source: the path to the file containing css. Makes errors and source maps more helpful, by letting them know where code comes from.

stringify(object, [options])

Accepts an AST object (as css.parse produces) and returns a CSS string.

options:

  • indent: the string used to indent the output. Defaults to two spaces.
  • compress: omit comments and extraneous whitespace.

Example

var ast = parse('body { font-size: 12px; }', { source: 'source.css' });

var css = stringify(ast);

Errors

Errors thrown during parsing have the following properties:

  • message: String. The full error message with the source position.
  • reason: String. The error message without position.
  • filename: String or undefined. The value of options.source if passed to css.parse. Otherwise undefined.
  • line: Integer.
  • column: Integer.
  • source: String. The portion of code that couldn't be parsed.

When parsing with the silent option, errors are listed in the parsingErrors property of the stylesheet node instead of being thrown.

If you create any errors in plugins such as in rework, you must set the same properties for consistency.

AST

Interactively explore the AST with http://iamdustan.com/reworkcss_ast_explorer/.

Common properties

All nodes have the following properties.

position

Information about the position in the source string that corresponds to the node.

Object:

  • start: Object:
    • line: Number.
    • column: Number.
  • end: Object:
    • line: Number.
    • column: Number.
  • source: String or undefined. The value of options.source if passed to css.parse. Otherwise undefined.
  • content: String. The full source string passed to css.parse.

The line and column numbers are 1-based: The first line is 1 and the first column of a line is 1 (not 0).

The position property lets you know from which source file the node comes from (if available), what that file contains, and what part of that file was parsed into the node.

type

String. The possible values are the ones listed in the Types section below.

parent

A reference to the parent node, or null if the node has no parent.

Types

The available values of node.type are listed below, as well as the available properties of each node (other than the common properties listed above.)

stylesheet

The root node returned by css.parse.

  • stylesheet: Object:
    • rules: Array of nodes with the types rule, comment and any of the at-rule types.
    • parsingErrors: Array of Errors. Errors collected during parsing when option silent is true.

rule

  • selectors: Array of Strings. The list of selectors of the rule, split on commas. Each selector is trimmed from whitespace and comments.
  • declarations: Array of nodes with the types declaration and comment.

declaration

  • property: String. The property name, trimmed from whitespace and comments. May not be empty.
  • value: String. The value of the property, trimmed from whitespace and comments. Empty values are allowed.

comment

A rule-level or declaration-level comment. Comments inside selectors, properties and values etc. are lost.

  • comment: String. The part between the starting /* and the ending */ of the comment, including whitespace.

charset

The @charset at-rule.

  • charset: String. The part following @charset .

custom-media

The @custom-media at-rule.

  • name: String. The ---prefixed name.
  • media: String. The part following the name.

document

The @document at-rule.

  • document: String. The part following @document .
  • vendor: String or undefined. The vendor prefix in @document, or undefined if there is none.
  • rules: Array of nodes with the types rule, comment and any of the at-rule types.

font-face

The @font-face at-rule.

  • declarations: Array of nodes with the types declaration and comment.

host

The @host at-rule.

  • rules: Array of nodes with the types rule, comment and any of the at-rule types.

import

The @import at-rule.

  • import: String. The part following @import .

keyframes

The @keyframes at-rule.

  • name: String. The name of the keyframes rule.
  • vendor: String or undefined. The vendor prefix in @keyframes, or undefined if there is none.
  • keyframes: Array of nodes with the types keyframe and comment.

keyframe

  • values: Array of Strings. The list of “selectors” of the keyframe rule, split on commas. Each “selector” is trimmed from whitespace.
  • declarations: Array of nodes with the types declaration and comment.

media

The @media at-rule.

  • media: String. The part following @media .
  • rules: Array of nodes with the types rule, comment and any of the at-rule types.

namespace

The @namespace at-rule.

  • namespace: String. The part following @namespace .

page

The @page at-rule.

  • selectors: Array of Strings. The list of selectors of the rule, split on commas. Each selector is trimmed from whitespace and comments.
  • declarations: Array of nodes with the types declaration and comment.

supports

The @supports at-rule.

  • supports: String. The part following @supports .
  • rules: Array of nodes with the types rule, comment and any of the at-rule types.

container

The @container at-rule.

  • conatiner: String. The part following @container .
  • rules: Array of nodes with the types rule, comment and any of the at-rule types.

layer

The @layer at-rule.

  • layer: String. The part following @layer .
  • rules: Array of nodes with the types rule, comment and any of the at-rule types. This may be null, if the rule did not contain any.

Example

CSS:

body {
  background: #eee;
  color: #888;
}

Parse tree:

{
  "type": "stylesheet",
  "stylesheet": {
    "rules": [
      {
        "type": "rule",
        "selectors": [
          "body"
        ],
        "declarations": [
          {
            "type": "declaration",
            "property": "background",
            "value": "#eee",
            "position": {
              "start": {
                "line": 2,
                "column": 3
              },
              "end": {
                "line": 2,
                "column": 19
              }
            }
          },
          {
            "type": "declaration",
            "property": "color",
            "value": "#888",
            "position": {
              "start": {
                "line": 3,
                "column": 3
              },
              "end": {
                "line": 3,
                "column": 14
              }
            }
          }
        ],
        "position": {
          "start": {
            "line": 1,
            "column": 1
          },
          "end": {
            "line": 4,
            "column": 2
          }
        }
      }
    ]
  }
}

License

MIT

css-tools's People

Contributors

andreypopp avatar bnjmnt4n avatar cone56 avatar conradz avatar daleeidd avatar denvdmj avatar dependabot[bot] avatar dineshks1 avatar dominicbarnes avatar forbeslindesay avatar fritz-c avatar github-actions[bot] avatar gmetais avatar holblin avatar iamdustan avatar jogibear9988 avatar jonathanong avatar kboedges avatar kevva avatar lydell avatar moox avatar necolas avatar paulclark avatar phistuck avatar siadcock avatar slexaxton avatar tj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

css-tools's Issues

@container support needs to be added

Expected Behaviour

@container rules should be parsed

Actual Behaviour

they are not parsed

Reproduce Scenario (including but not limited to)

@container (min-width: 700px) {
  .card h2 {
    font-size: 2em;
  }
}

we should support stylesheets with errors

at the monent a styleshee like this:

  * {
  aa;
  display: block;
  }

or this:

  * {
  aa;
  display: block;
  }

crashes the parser. but we should look if we could still continue parseing and return the valid styles (same as the browser does)

nicer formating of grid-template-areas

Expected Behaviour

template areas should be stringified like this:

grid-template-areas: "menu head head head"
                     "menu main main main"
                     "menu main main main"
                     "menu footer footer footer";

Actual Behaviour

grid-template-areas: "menu head head head"
"menu main main main"
"menu main main main"
"menu footer footer footer";

Requiring this package as a dependency fails

Expected Behaviour

var cssTools = require('@adobe/css-tools');

This should let me use cssTools.parse().

Actual Behaviour

When trying to use matchers from @testing-library/jest-dom I get the following error message:
Cannot find module '@adobe/css-tools' from 'node_modules/@testing-library/jest-dom/dist/matchers-d012a0f1.js'

Reproduce Scenario (including but not limited to)

Steps to Reproduce

Platform and Version

Sample Code that illustrates the problem

Logs taken while reproducing problem

@layer support needs to be added

Expected Behaviour

@layer rules should be parsed

Actual Behaviour

they are not parsed, see also: reworkcss#165

Reproduce Scenario (including but not limited to)

var css = require('css');
var obj = css.parse("@layer mylayer {.card {--content-box-flex-grow: 1; display: flex; flex-direction: column; position: relative; width: 100%;} }");
css.stringify(obj);

Can't parse @apply

It seems that @apply can't be parsed properly? reworkcss/css also has this problem and I tried with an open PR that they have but I couldn't make it work.

[Error]: :176:3: missing '}'
    at h (/self/project/node_modules/@adobe/css-tools/dist/umd/cssTools.js:1:2043)
    at j (/self/project/node_modules/@adobe/css-tools/dist/umd/cssTools.js:1:3488)
    at M (/self/project/node_modules/@adobe/css-tools/dist/umd/cssTools.js:1:5918)
    at d (/self/project/node_modules/@adobe/css-tools/dist/umd/cssTools.js:1:2283)
    at /self/project/node_modules/@adobe/css-tools/dist/umd/cssTools.js:1:5979
    at Object.e.parse (/self/project/node_modules/@adobe/css-tools/dist/umd/cssTools.js:1:6086) {
  reason: "missing '}'",
  filename: '',
  line: 176,
  column: 3,
  source: '@apply font-bold py-2 px-4 rounded;\n' +
    '}\n' +
.div {
  @apply font-bold py-2 px-4 rounded;
}

It's not priority as I can remove the @apply but it would be cool to support it?

perfomance optimizations

as we are getting more at-rules, maybe we should check, if it would be better to check with startsWith, before we use all the regexes.
and also do we look if first char is a @?

for me perfomance is not so relevant, but as you already did some meassurement, maybe for you is?

@page and @left-middle crashes the parser

Expected Behaviour

@page {
    @left-middle {
        margin: 0;
    }
}

should parse without an error

Actual Behaviour

It throws the following :

input.css:29:5: @page missing '}'

  77 |
  78 |   function error(msg: string) {
> 79 |     const err = new CssParseError(
     |                 ^
  80 |       options?.source || '',
  81 |       msg,
  82 |       lineno,

  at error (src/parse/index.ts:79:17)
  at atpage (src/parse/index.ts:562:14)
  at atrule (src/parse/index.ts:693:7)
  at rules (src/parse/index.ts:134:59)
  at stylesheet (src/parse/index.ts:98:23)
  at parse (src/parse/index.ts:720:20)
  at parseInput (test/cases.test.ts:39:19)
  at Object.<anonymous> (test/cases.test.ts:31:36)

Reproduce Scenario (including but not limited to)

Apply following git patch :

diff --git a/test/cases/media/input.css b/test/cases/media/input.css
index 8ab3a73..fa567e0 100644
--- a/test/cases/media/input.css
+++ b/test/cases/media/input.css
@@ -24,3 +24,9 @@
     border: 0.5pt solid #666;
   }
 }
+
+@page {
+    @left-middle {
+        margin: 0;
+    }
+}

Steps to Reproduce

Run npm test in the css-tools repository

Platform and Version

Sample Code that illustrates the problem

Logs taken while reproducing problem

Problem parsing :is()

Expected Behaviour

The content of the :is() is a selector itself ( https://developer.mozilla.org/en-US/docs/Web/CSS/:is )

Actual Behaviour

Currently, parsing selector as in the following code split the selector with the , which doesn't work

Reproduce Scenario (including but not limited to)

Using a :is() with a , inside the parenthesis

Steps to Reproduce

Platform and Version

Linux, Node 18

Sample Code that illustrates the problem

import {parse} from "@adobe/css-tools";
console.dir(await parse('.klass:is(:nth-child(1), :nth-child(2)) {margin: 0 !important}'), { depth: 4 });

Logs taken while reproducing problem

yarn node test.js
{
  type: 'stylesheet',
  stylesheet: {
    source: undefined,
    rules: [
      {
        type: 'rule',
        selectors: [ '.klass:is(:nth-child(1)', ':nth-child(2))' ],
        declarations: [ [Object] ],
        position: $0865a9fb4cc365fe$export$2e2bcd8739ae039 {
          start: [Object],
          end: [Object],
          source: ''
        }
      }
    ],
    parsingErrors: []
  }
}

Interim 3.0.1 release?

Hello! The rewrite looks great, looking forward do it. I'm not sure how big of a request it is to cut a release from the old code, but I'm coming from styled-components/jest-styled-components#356 which is a bug that is blocked by a bug whose fix was merged into the original repo, but never released. That fix and a change to the Travis config are literally the only changes from 3.0.0 and it's a very simple (and tested) fix, so it should be pretty safe: https://github.com/reworkcss/css/commits/master

For expedience's sake would it be possible to release a 3.0.1 version from 434aa17 to this package while the 4.0 release is being finalized? There's currently no published version of the package with that fix in place. It looks like the release process wasn't terribly complicated - there's no build process or anything, the published package appears to be just the repo as-is, with test/, benchmark/, and the dotfiles stripped out.

Having a 3.0.1 released (or even a prerelease, if you don't want an official release confusing folks) would help us get unstuck there, and since you're planning on maintaining a fork anyway, it would be nice to be able to depend on a fork that will be updated rather than rolling our own.

Either way, thanks taking this on!

@import does not work if url contains ';'

Expected Behaviour

This should be parsed correctly

let demo = cssCompiler.parse(`
@import url('https://fonts.googleapis.com/css2?family=Zen+Kaku+Gothic+New:wght@300;400;500;700;900&display=swap');
.test {
  background: red;
  font-family: 'Zen Kaku Gothic New', sans-serif;
}
`)

console.log('demo', demo)

Actual Behaviour

`@import url('https://fonts.googleapis.com/css2?family=Zen+Kaku+Gothic+New:wght@300``

Becomes the first declaration and 400;500;700;900&display=swap')the second.

Reproduce Scenario (including but not limited to)

Steps to Reproduce

Run the code above

Platform and Version

4.0.1

Sample Code that illustrates the problem

Look ahead

Logs taken while reproducing problem

N/A

Regression in selector parsing: Attribute selectors not parsed correctly

Thank you for this fork of css. It's very helpful to us.

While testing, I think I have found a regression in the parser, introduced with commit 24ed6e7:

/**
* replace ',' by \u200C for data selector (div[data-lang="fr,de,us"])
* replace ',' by \u200C for nthChild and other selector (div:nth-child(2,3,4))
*
* Examples:
* div[data-lang="fr,\"de,us"]
* div[data-lang='fr,\'de,us']
* div:matches(.toto, .titi:matches(.toto, .titi))
*
* Regex logic:
* ("|')(?:\\\1|.)*?,(?:\\\1|.)*?\1 => Handle the " and '
* \(.*?,.*?\) => Handle the ()
*
* Optimization 0:
* No greedy capture (see docs about the difference between .* and .*?)
*
* Optimization 1:
* \(.*?,.*?\) instead of \(.*?\) to limit the number of replace (don't need to replace if , is not in the string)
*
* Optimization 2:
* ("|')(?:\\\1|.)*?,(?:\\\1|.)*?\1 this use reference to capture group, it work faster.
*/
.replace(/("|')(?:\\\1|.)*?,(?:\\\1|.)*?\1|\(.*?,.*?\)/g, m =>
m.replace(/,/g, '\u200C')
)

The regular expression /("|')(?:\\\1|.)*?,(?:\\\1|.)*?\1|\(.*?,.*?\)/g seems to be too greedy for cases like div[class='foo'],div[class='bar']. For this example, it captures 'foo'],div[class=', leading to an incorrect replacement of the comma that separates the two selectors and is not part of the data selector.

The original regular expression, used before this change (/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g) handles this case correctly by matching 'foo' and 'bar'.

Ultimately, the current behavior leads to an incorrect AST, where instead of two selectors, only one (incorrect) selector is listed:

"type": "rule",
"selectors": [
    "div[data-value='foo'],div[data-value='bar']",
],

Expected:

"type": "rule",
"selectors": [
    "div[data-value='foo']",
    "div[data-value='bar']"
],

I have attached a test case demonstrating this. You can extract the archive directly into test/cases/:
case - selectors-attributes.zip

esm build is missing ".js" extensions

any idea, why the newest package is missing the ".js" extension in the ES6 modules?
Without them, the imports are not directly usable in the browser.

Incorrect comment handling in some edge-case

Expected Behaviour

Comments are properly ignored in all places.

Actual Behaviour

Comment in the selector that contains a { break the parsing because the { evaluation is done before the comment escaping.

Steps to Reproduce

  • Change the file test/cases/comment/input.css by:
/* 1 */

head, /* footer, */body/*, nav */ { /* 2 */
  /* 3 */
  /**/foo: 'bar';
  /* 4 */
} /* 5 */

/* 6 */
  • Run yarn test

Logs taken while reproducing the problem

$jest
 PASS  test/parse.test.ts
 FAIL  test/cases.test.ts
  ● cases/comment › should match ast.json

    input.css:3:37: property missing ':'

      77 |
      78 |   function error(msg: string) {
    > 79 |     const err = new CssParseError(
         |                 ^
      80 |       options?.source || '',
      81 |       msg,
      82 |       lineno,

      at error (src/parse/index.ts:79:17)
      at declaration (src/parse/index.ts:263:14)
      at declarations (src/parse/index.ts:294:20)
      at rule (src/parse/index.ts:716:21)
      at rules (src/parse/index.ts:134:71)
      at stylesheet (src/parse/index.ts:98:23)
      at parse (src/parse/index.ts:720:20)
      at parseInput (test/cases.test.ts:39:19)
      at Object.<anonymous> (test/cases.test.ts:15:19)

Potential fix

  • Add a special condition in the regex for the selector to skip the comments. This should not reduce performance or introduce a dos vulnerability.
  • Change the parsing to skip the comment while doing the parsing.

Can no longer import css-tools 4.1.0+ from *.mjs file

When using @adobe/css-tools 4.0.1 and 4.0.2 I was able to do:

import cssTools from '@adobe/css-tools';

from my script.mjs file (executed with node script.mjs in my package.json)

Since 4.1.0 was released this no longer works and I get the error:

(node:2617) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
/Users/michaelpotter/Projects/outline/node_modules/@adobe/css-tools/dist/esm/index.js:1
export { default as parse } from './parse';

Node version is 16.16.0, which I'm stuck with on this project. For now I'm going to pin my version of cssTools to 4.0.2 but interested in how I should be using the latest version.

Issue when using module indirectly using Create-React-App and import

I've struggled with this issue for several hours already, so I'm going to summarize my findings so far and maybe there is something you could help me with.

I currently am providing a package to some other users who are using my package using import syntax.

In the package I provide, I'm using require to load the @adobe/css-tools package, as it seemed to me that it supports both import or require.

I've published a very simple library, called css-wrapper-xxx.

In the css-wrapper-xxx library, I simply wrote, in order to compare how lodash and @adobe/css-tools behave :

const adobe = require("@adobe/css-tools");
const lodash = require("lodash");
module.exports = {adobe, lodash};

I then want to use my "css-wrapper-xxx" library from a Create-React-App project, which you can get up and running using following commands :

git clone https://github.com/edi9999/repro-adobe-css-tools.git
cd repro-adobe-css-tools
npm install
npm start
# open browser on localhost:3000/

When I log the values returned by the css-wrapper-xxx library, for some strange reason, the value for "adobe" is a path and not the object that should've been exported.

Selection_001
Selection_002
assets/2071336/563cf962-6ea0-4be0-8e79-2c94a207d79e)

Expected Behaviour

I would expect the require to work

Actual Behaviour

The require (from css-wrapper-xxx) inside then import (from the create-react-app project) returns a string

Notes / Solutions

I'm not sure whether this issue is directly related to css-tools, or if this is a webpack or even a CreateReactApp issue.

TypeScript issue: `moduleResolution:NodeNext` doesn’t work with this library

The problem

tsconfig.json:

"module": "NodeNext",
"moduleResolution": "NodeNext",

TypeScript code:

import { parse } from '@adobe/css-tools';

Result – error from tsc:

  • Could not find a declaration file for module '@adobe/css-tools'. '~/my-lib/node_modules/@adobe/css-tools/dist/index.mjs' implicitly has an 'any' type.
    • There are types at '~/my-lib/node_modules/@adobe/css-tools/dist/types.d.ts', but this result could not be resolved when respecting package.json "exports". The '@adobe/css-tools' library may need to update its package.json or typings.

Solutions

Quickest fix – change package.json:

"exports": {
  "import": "./dist/index.mjs",
  "types": "./dist/types.d.ts",
  "require": "./dist/index.cjs"
},

In the long run, you don’t need "main", "module" and "types" (anywhere!) anymore, as these are superseded by "exports" and modern TypeScript .d.ts resolution. You only have to rename:

mv types.d.ts index.d.mts

Possibly useful

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.