proscenium 0.1.0.alpha2-x86_64-darwin → 0.1.0.alpha4-x86_64-darwin

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +216 -32
  3. data/app/components/react_component.rb +10 -3
  4. data/bin/esbuild +0 -0
  5. data/bin/{parcel_css → lightningcss} +0 -0
  6. data/config/routes.rb +1 -1
  7. data/lib/proscenium/compilers/esbuild/compile_error.js +147 -0
  8. data/lib/proscenium/compilers/esbuild/css/postcss.js +67 -0
  9. data/lib/proscenium/compilers/esbuild/css_plugin.js +176 -0
  10. data/lib/proscenium/compilers/esbuild/env_plugin.js +6 -4
  11. data/lib/proscenium/compilers/esbuild/http_bundle_plugin.js +53 -0
  12. data/lib/proscenium/compilers/esbuild/import_map.js +59 -0
  13. data/lib/proscenium/compilers/esbuild/resolve_plugin.js +67 -65
  14. data/lib/proscenium/compilers/esbuild/setup_plugin.js +32 -22
  15. data/lib/proscenium/{cli → compilers}/esbuild/solidjs_plugin.js +5 -4
  16. data/lib/proscenium/compilers/esbuild.bench.js +7 -3
  17. data/lib/proscenium/compilers/esbuild.js +93 -19
  18. data/lib/proscenium/css_module.rb +48 -6
  19. data/lib/proscenium/helper.rb +5 -3
  20. data/lib/proscenium/middleware/base.rb +13 -3
  21. data/lib/proscenium/middleware/esbuild.rb +17 -1
  22. data/lib/proscenium/middleware/lightningcss.rb +64 -0
  23. data/lib/proscenium/middleware.rb +7 -19
  24. data/lib/proscenium/phlex.rb +36 -0
  25. data/lib/proscenium/railtie.rb +10 -20
  26. data/lib/proscenium/runtime/auto_reload.js +3 -3
  27. data/lib/proscenium/side_load.rb +14 -40
  28. data/lib/proscenium/utils.js +8 -0
  29. data/lib/proscenium/version.rb +1 -1
  30. data/lib/proscenium/view_component.rb +5 -1
  31. data/lib/proscenium.rb +1 -0
  32. metadata +27 -19
  33. data/lib/proscenium/cli/argument_error.js +0 -24
  34. data/lib/proscenium/cli/builders/index.js +0 -1
  35. data/lib/proscenium/cli/builders/javascript.js +0 -45
  36. data/lib/proscenium/cli/builders/react.js +0 -60
  37. data/lib/proscenium/cli/builders/solid.js +0 -46
  38. data/lib/proscenium/cli/esbuild/env_plugin.js +0 -21
  39. data/lib/proscenium/cli/esbuild/resolve_plugin.js +0 -136
  40. data/lib/proscenium/cli/js_builder.js +0 -194
  41. data/lib/proscenium/cli/solid.js +0 -15
  42. data/lib/proscenium/cli/utils.js +0 -93
  43. data/lib/proscenium/middleware/parcel_css.rb +0 -37
  44. data/lib/proscenium/runtime/component_manager/index.js +0 -27
  45. data/lib/proscenium/runtime/component_manager/render_component.js +0 -40
  46. data/lib/proscenium/runtime/import_css.js +0 -46
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d33e162bf11cfad365e816b4dbb6b414af4e27554a9a241a68f0e511d3304120
4
- data.tar.gz: a308f22364895470325ec64ef039ce57d635228c0c38af2b05f26905c23f5b10
3
+ metadata.gz: f7479881d2ace4e2930975f02eee4c5085f2355e06d35d689ca419ec0a4eb6a0
4
+ data.tar.gz: 216ac3339281bc6c7b526ba25d6f228fe01b855d14071a8b74eddf8d8ae94354
5
5
  SHA512:
6
- metadata.gz: c602130d2ceb6a3fb19f3eff6c90c433903f7727aca48d0f474c6ee0b8863a3f7ba355572a4f06fa2005b3f00d708a9c374f061c6314991630558ba177c6ec3d
7
- data.tar.gz: 77205b53c2bbbf504724c82dc44f745f8268469f68b2f955d3a6a85bf9c45402cf1559054a4e23bae419d7b57340a07be5fa6db81b904d7b7bb133b282639b4c
6
+ metadata.gz: 28c0ea45615d6eaa21bb5d761df7eab863f4b7ce81ea747514b521ebf23bb2ad1baa9a8a3172a423a21da0b11d58f0a0104c2ddb18660d765ac9b8827c36c319
7
+ data.tar.gz: 1289a1eeeabf08d25f7183fd5d300fe0ac60162784c25ba719039cd3bbe91739541f64d3690d98c75a288bfc660339d601b56ee60aa1bfa21ac149557538b878
data/README.md CHANGED
@@ -1,65 +1,250 @@
1
- # Proscenium
2
-
3
- - Serve assets from anywhere within the Rails root. (eg. `/app/views/layouts/application.css`, `/lib/utils/time.js`)
4
- - Side loaded JS/CSS for your layouts and views.
5
- - JS imports
6
- - Dynamic imports
7
- - Real-time bundling of JS, JSX and CSS.
8
- - Import CSS and other static assets (images, fonts, etc.)
9
-
10
- ## WANT
11
-
12
- - Nested CSS
13
- - Import CSS Modules
1
+ # Proscenium - Modern Client-Side Tooling for Rails
2
+
3
+ Proscenium treats your client-side code as first class citizens of your Rails app, and assumes a
4
+ "fast by default" internet. It compiles your JS, JSX and CSS in real time, and on demand, with no
5
+ configuration at all!
6
+
7
+ - Zero configuration.
8
+ - NO JavaScript rumtime needed - just the browser!
9
+ - Real-time compilation.
10
+ - No additional process or server - Just run Rails!
11
+ - Serve assets from anywhere within your Rails root (/app, /config, /lib).
12
+ - Automatically side load JS/CSS for your layouts and views.
13
+ - Import JS(X) and CSS from node_modules, URL, local (relative, absolute).
14
+ - Optional bundling of JS(X) and CSS.
15
+ - Import Map support for JS and CSS.
16
+ - CSS Modules.
17
+ - CSS Custom Media Queries.
18
+ - CSS mixins.
19
+ - Minification.
20
+ - Auto reload after changes (development only).
21
+
22
+ ## !! EXPERIMENTAL SOFTWARE !!
23
+
24
+ While my goal is to use Proscenium in production, I strongly recommended that you **DO NOT** use
25
+ this in production apps! Right now, this is a play thing, and should only be used for
26
+ development/testing.
14
27
 
15
28
  ## Installation
16
29
 
17
- Add this line to your application's Gemfile:
30
+ Add this line to your application's Gemfile, and you're good to go:
18
31
 
19
32
  ```ruby
20
33
  gem 'proscenium'
21
34
  ```
22
35
 
23
- ## Usage
36
+ ## Frontend Code Anywhere!
37
+
38
+ Proscenium believes that your frontend code is just as important as your backend code, and is not an
39
+ afterthought - they should be first class citizens of your Rails app. So instead of throwing all
40
+ your JS and CSS into a "app/assets" directory, put them wherever you want!
41
+
42
+ For example, if you have JS that is used by your `UsersController#index`, then put it in
43
+ `app/views/users/index.js`. Or if you have some CSS that is used by your entire application, put it
44
+ in `app/views/layouts/application.css`. Maybe you have a few JS utility functions, so put them in
45
+ `lib/utils.js`.
46
+
47
+ Simply create your JS(X) and CSS anywhere you want, and they will be served by your Rails app.
48
+
49
+ Using the examples above...
50
+
51
+ - `app/views/users/index.js` => `https://yourapp.com/app/views/users/index.js`
52
+ - `app/views/layouts/application.css` => `https://yourapp.com/app/views/layouts/application.css`
53
+ - `lib/utils.js` => `https://yourapp.com/lib/utils.js`
54
+ - `config/properties.css` => `https://yourapp.com/config/properties.css`
55
+
56
+ ## Importing
57
+
58
+ Proscenium supports importing JS and CSS from `node_modules`, URL, and local (relative, absolute).
59
+
60
+ Imports are assumed to be JS files, so there is no need to specify the file extesnion in such cases.
61
+ But you can if you like. All other file types must be specified using their full file name and
62
+ extension.
63
+
64
+ ### URL Imports
65
+
66
+ Any import beginning with `http://` or `https://` will be fetched from the URL provided:
67
+
68
+ ```js
69
+ import React from 'https://esm.sh/react`
70
+ ```
71
+
72
+ ```css
73
+ @import 'https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css';
74
+ ```
75
+
76
+ ### Import from NPM (`node_modules`)
77
+
78
+ Bare imports (imports not beginning with `./`, `/`, `https://`, `http://`) are fully supported, and
79
+ will use your package manager of choice (eg, NPM, Yarn, pnpm):
80
+
81
+ ```js
82
+ import React from 'react`
83
+ ```
84
+
85
+ ### Local Imports
86
+
87
+ And of course you can import your own code, using relative or absolute paths (file extension is
88
+ optional):
89
+
90
+ ```js /app/views/layouts/application.js
91
+ import utils from '/lib/utils'
92
+ ```
93
+
94
+ ```js /lib/utils.js
95
+ import constants from './constants'
96
+ ```
97
+
98
+ ## Bundling
99
+
100
+ Proscenium does not do any bundling, as we believe that **the web is now fast by default**. So we
101
+ let you decide if and when to bundle your code using query parameters in your JS and CSS imports.
102
+
103
+ ```js
104
+ import doStuff from 'stuff?bundle'
105
+ doStuff()
106
+ ```
107
+
108
+ Bundling a URL import is not supported, as the URL itself may also support query parameters,
109
+ resulting in conflicts. For example, esm.sh also supports a `?bundle` param, bundling a module's
110
+ dependencies into a single file. Instead, you should install the module locally using your favourite
111
+ package manager.
112
+
113
+ ## Import Map
114
+
115
+ Import map for both JS and CSS is supported out of the box, and works with no regard to the browser
116
+ version being used. Just create `config/import_map.json`:
117
+
118
+ ```json
119
+ {
120
+ "imports": {
121
+ "react": "https://esm.sh/react@18.2.0",
122
+ "start": "/lib/start.js",
123
+ "common": "/lib/common.css",
124
+ "@radix-ui/colors/": "https://esm.sh/@radix-ui/colors@0.1.8/",
125
+ }
126
+ }
127
+ ```
128
+
129
+ Using the above import map, we can do...
130
+
131
+ ```js
132
+ import { useCallback } from 'react'
133
+ import startHere from 'start'
134
+ import styles from 'common'
135
+ ```
136
+
137
+ and for CSS...
138
+
139
+ ```css
140
+ @import 'common';
141
+ @import '@radix-ui/colors/blue.css';
142
+ ```
143
+
144
+ You can instead write your import map in Javascript instead of JSON. So instead of
145
+ `config/import_map.json`, create `config/import_map.js`, and define an anonymous function. This
146
+ function accepts a single `environment` argument.
147
+
148
+ ```js
149
+ env => ({
150
+ imports: {
151
+ react: env === 'development' ? 'https://esm.sh/react@18.2.0?dev' : 'https://esm.sh/react@18.2.0'
152
+ }
153
+ })
154
+ ```
24
155
 
25
- ### Side Loading
156
+ ## Side Loading
26
157
 
27
- Proscenium has built in support for automatically side loading JS and CSS with your views and layouts.
158
+ Proscenium has built in support for automatically side loading JS and CSS with your views and
159
+ layouts.
28
160
 
29
- Just create a JS and/or CSS file with the same name as any view or layout, and make sure your layouts include `<%= yield :side_loaded_js %>` and `<%= yield :side_loaded_css %>`. Something like this:
161
+ Just create a JS and/or CSS file with the same name as any view or layout, and make sure your
162
+ layouts include `<%= side_load_stylesheets %>` and `<%= side_load_javascripts %>`. Something like
163
+ this:
30
164
 
31
165
  ```html
32
166
  <!DOCTYPE html>
33
167
  <html>
34
168
  <head>
35
169
  <title>Hello World</title>
36
- <%= yield :side_loaded_css %>
170
+ <%= side_load_stylesheets %>
37
171
  </head>
38
172
  <body>
39
- <%= yield %> <%= yield :side_loaded_js %>
173
+ <%= yield %>
174
+ <%= side_load_javascripts defer: true, type: 'module' %>
40
175
  </body>
41
176
  </html>
42
177
  ```
43
178
 
44
- On each page request, Proscenium will check if your layout and view has a JS/CSS file of the same name, and include them into your layout HTML.
179
+ On each page request, Proscenium will check if your layout and view has a JS/CSS file of the same
180
+ name, and include them into your layout HTML. Partials are not side loaded.
181
+
182
+ Side loading is enabled by default, but you can disable it by setting `config.proscenium.side_load`
183
+ to `false`.
45
184
 
46
- ### Importing in JS with `import`
185
+ ## CSS Modules
47
186
 
48
- Imports that do not begin with a `./` or `/` are bare imports, and will import a package using your local Node resolution algorithm.
187
+ Give any CSS file a `.module.css` extension, and Proscenium will load it as a CSS Module...
49
188
 
50
- `import 'react'`
51
- `import React as * from 'react'`
52
- `import { useState } from 'react'`
189
+ ```css
190
+ .header {
191
+ background-color: #00f;
192
+ }
193
+ ```
194
+
195
+ The above produces:
53
196
 
54
- Absolute and relative import paths are supported (`/*`, `./*`), and will behave as you would expect.
197
+ ```css
198
+ .header5564cdbb {
199
+ background-color: #00f;
200
+ }
201
+ ```
55
202
 
56
- Imports are assumed to be JS files, so there is no need to specify the file extesnion in such cases. But you can if you like. All other file types must be specified using their fill file name and extension.
203
+ Importing a CSS file from JS will automatically append the stylesheet to the document's head. The
204
+ results of the import will be an object of CSS modules.
57
205
 
58
- ### CSS
206
+ ```js
207
+ import styles from './styles.module.css'
208
+ ```
59
209
 
60
- Direct access to CSS files are parsed through @parcel/css.
210
+ ## Auto Reload
61
211
 
62
- Importing a CSS file from JS will append the CSS file to the document's head. The results of the import will be an object of CSS modules.
212
+ To aid fast development, Proscenium comes with an auto reload feature that will automatically reload
213
+ the page when any files changes. It is enabled by default in development, and requires that you
214
+ mount the Proscenium Railtie into your `config/routes.rb` file:
215
+
216
+ ```ruby
217
+ mount Proscenium::Railtie, at: '/proscenium' if Rails.env.development?
218
+ ```
219
+
220
+ Changes to CSS/JS(X) files in your `app` and `lib` directories will cause the page to reload.
221
+
222
+ NOTE: that this is hot module reloading (HMR) - a full page reload is triggered.
223
+
224
+ You can disable auto reload by setting the `config.proscenium.auto_reload` config option to false.
225
+
226
+ ## CSS Custom Media Queries
227
+
228
+ Proscenium supports [custom media queries](https://css-tricks.com/can-we-have-custom-media-queries-please/) as per the [spec](https://www.w3.org/TR/mediaqueries-5/#custom-mq). However, because of the way they are parsed, they cannot be imported using `@import`. So if you define your custom media queries in `/config/custom_media_queries.css`, Proscenium will automatically inject them into your CSS, so you can use them anywhere.
229
+
230
+ ## CSS Mixins
231
+
232
+ CSS mixins are supported using the `@mixin` at-rule. Simply define your mixins in any number of files ending in `.mixin.css`, and using the `@define-mixin` at-rule...
233
+
234
+ ```css
235
+ // /lib/text.mixin.css
236
+ @define-mixin bigText {
237
+ font-size: 50px;
238
+ }
239
+ ```
240
+
241
+ ```css
242
+ // /app/views/layouts/application.css
243
+ p {
244
+ @mixin bigText;
245
+ color: red;
246
+ }
247
+ ```
63
248
 
64
249
  ## How It Works
65
250
 
@@ -77,7 +262,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
77
262
 
78
263
  `deno compile --no-config -o bin/compilers/esbuild --import-map import_map.json -A lib/proscenium/compilers/esbuild.js`
79
264
 
80
-
81
265
  ## Contributing
82
266
 
83
267
  Bug reports and pull requests are welcome on GitHub at https://github.com/joelmoss/proscenium. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/joelmoss/proscenium/blob/master/CODE_OF_CONDUCT.md).
@@ -1,14 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ReactComponent < ApplicationComponent
4
- attr_accessor :props
4
+ attr_accessor :props, :lazy
5
5
 
6
- def initialize(props = {})
6
+ # @param props: [Hash]
7
+ # @param lazy: [Boolean] Lazy load the component using IntersectionObserver. Default: true.
8
+ def initialize(props: {}, lazy: true)
7
9
  @props = props
10
+ @lazy = lazy
11
+
8
12
  super
9
13
  end
10
14
 
11
15
  def call
12
- tag.div data: { component: { path: virtual_path, props: @props } }
16
+ tag.div class: ['componentManagedByProscenium', css_module(:component)],
17
+ data: { component: { path: virtual_path, props: props, lazy: lazy } } do
18
+ tag.div content
19
+ end
13
20
  end
14
21
  end
data/bin/esbuild CHANGED
Binary file
Binary file
data/config/routes.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  Proscenium::Railtie.routes.draw do
2
- if Proscenium.config.auto_refresh
2
+ if Proscenium.config.auto_reload
3
3
  mount Proscenium::Railtie.websocket => Proscenium.config.cable_mount_path
4
4
  end
5
5
  end
@@ -0,0 +1,147 @@
1
+ export default function () {
2
+ if (Deno.env.get('RAILS_ENV') === 'development') {
3
+ return function (detail) {
4
+ const template = `
5
+ <style>
6
+ :host {
7
+ position: fixed;
8
+ z-index: 99999;
9
+ top: 0;
10
+ left: 0;
11
+ width: 100%;
12
+ height: 100%;
13
+ overflow-y: scroll;
14
+ margin: 0;
15
+ background: rgba(0, 0, 0, 0.66);
16
+ display: flex;
17
+ align-items: center;
18
+ --monospace: 'SFMono-Regular', Consolas,
19
+ 'Liberation Mono', Menlo, Courier, monospace;
20
+ --red: #ff5555;
21
+ --yellow: #e2aa53;
22
+ --purple: #cfa4ff;
23
+ --cyan: #2dd9da;
24
+ --dim: #c9c9c9;
25
+ }
26
+
27
+ .window {
28
+ font-family: var(--monospace);
29
+ line-height: 1.5;
30
+ width: 800px;
31
+ color: #d8d8d8;
32
+ margin: 30px auto;
33
+ padding: 25px 40px;
34
+ position: relative;
35
+ background: #181818;
36
+ border-radius: 6px 6px 8px 8px;
37
+ box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22);
38
+ overflow: hidden;
39
+ border-top: 8px solid var(--red);
40
+ }
41
+
42
+ pre {
43
+ font-family: var(--monospace);
44
+ font-size: 16px;
45
+ margin: 0 0 1em 0;
46
+ overflow-x: scroll;
47
+ scrollbar-width: none;
48
+ }
49
+
50
+ pre::-webkit-scrollbar {
51
+ display: none;
52
+ }
53
+
54
+ .title, .message {
55
+ line-height: 1.3;
56
+ font-weight: 600;
57
+ white-space: pre-wrap;
58
+ }
59
+
60
+ .message-body {
61
+ color: var(--red);
62
+ }
63
+
64
+ .file {
65
+ color: var(--cyan);
66
+ margin-bottom: 0;
67
+ white-space: pre-wrap;
68
+ word-break: break-all;
69
+ }
70
+
71
+ .code {
72
+ background: black;
73
+ border-left: 3px solid gray;
74
+ padding: 10px 0 0 20px;
75
+ }
76
+ .lineText {
77
+ display: block;
78
+ white-space: pre-wrap;
79
+ }
80
+ .lineCursor {
81
+ white-space: pre;
82
+ color: blueviolet;
83
+ display: block;
84
+ }
85
+ </style>
86
+ <div class="window">
87
+ <pre class="title">COMPILE ERROR!</pre>
88
+ <pre class="message"><span class="message-body"></span> in <span class="file"></span></pre>
89
+ <pre class="code"><span class="lineText"></span><span class="lineCursor"></span></pre>
90
+ </div>
91
+ `
92
+
93
+ class ErrorOverlay extends HTMLElement {
94
+ constructor(err) {
95
+ super()
96
+
97
+ this.root = this.attachShadow({ mode: 'open' })
98
+ this.root.innerHTML = template
99
+ this.root.querySelector('.message-body').textContent = err.text.trim()
100
+ if (err.location) {
101
+ const location = [err.location.file]
102
+ err.location.line && location.push(err.location.line)
103
+ err.location.column && location.push(err.location.column)
104
+ this.root.querySelector('.file').textContent = `/${location.join(':')}`
105
+
106
+ if (err.location.lineText) {
107
+ this.root.querySelector('.lineText').textContent = err.location.lineText
108
+ this.root.querySelector('.lineCursor').textContent =
109
+ ''.padStart(err.location.column - 1, ' ') + '^'
110
+ } else {
111
+ this.root.querySelector('.code').remove()
112
+ }
113
+
114
+ console.error('%s at %O', err.text, location)
115
+ } else {
116
+ console.error(err.text)
117
+ }
118
+
119
+ this.root.querySelector('.window').addEventListener('click', e => {
120
+ e.stopPropagation()
121
+ })
122
+
123
+ this.addEventListener('click', () => {
124
+ this.close()
125
+ })
126
+ }
127
+
128
+ close() {
129
+ this.parentNode?.removeChild(this)
130
+ }
131
+ }
132
+
133
+ const overlayId = 'proscenium-error-overlay'
134
+ if (customElements && !customElements.get(overlayId)) {
135
+ customElements.define(overlayId, ErrorOverlay)
136
+ }
137
+
138
+ document.body.appendChild(new ErrorOverlay(detail))
139
+ }
140
+ } else {
141
+ return function (detail) {
142
+ const location = `/${detail.location.file}:${detail.location.line}:${detail.location.column}`
143
+ console.error('%s at %O', detail.text, location)
144
+ throw `${detail.text} at ${location}`
145
+ }
146
+ }
147
+ }
@@ -0,0 +1,67 @@
1
+ import { expandGlob } from 'std/fs/mod.ts'
2
+ import postcss, { CssSyntaxError } from 'postcss'
3
+
4
+ export default async (root, path) => {
5
+ let tmpFile
6
+ let contents
7
+
8
+ const mixinFiles = []
9
+ for await (const file of expandGlob(`lib/**/*.mixin.css`, { root })) {
10
+ mixinFiles.push(file.path)
11
+ }
12
+
13
+ // Only process mixins with PostCSS if there are any 'lib/**/*.mixin.css' files.
14
+ if (mixinFiles.length > 0) {
15
+ tmpFile = await Deno.makeTempFile()
16
+ contents = await Deno.readTextFile(path)
17
+
18
+ const result = await postcss([mixinsPlugin({ mixinFiles })]).process(contents, { from: path })
19
+ contents = result.css
20
+ }
21
+
22
+ return [tmpFile, contents]
23
+ }
24
+
25
+ const mixinsPlugin = (opts = {}) => {
26
+ return {
27
+ postcssPlugin: 'mixins',
28
+
29
+ prepare() {
30
+ const mixins = {}
31
+
32
+ return {
33
+ async Once(_, helpers) {
34
+ for (const path of opts.mixinFiles) {
35
+ const content = await Deno.readTextFile(path)
36
+ const root = helpers.parse(content, { from: path })
37
+
38
+ root.walkAtRules('define-mixin', atrule => {
39
+ mixins[atrule.params] = atrule
40
+ })
41
+ }
42
+ },
43
+
44
+ AtRule: {
45
+ mixin: (rule, helpers) => {
46
+ const mixin = mixins[rule.params]
47
+
48
+ if (!mixin) {
49
+ throw rule.error(`Undefined mixin '${rule.params}'`)
50
+ }
51
+
52
+ const proxy = new helpers.Root()
53
+ for (let i = 0; i < mixin.nodes.length; i++) {
54
+ const node = mixin.nodes[i].clone()
55
+ delete node.raws.before
56
+ proxy.append(node)
57
+ }
58
+
59
+ rule.parent.insertBefore(rule, proxy)
60
+
61
+ if (rule.parent) rule.remove()
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }