shopify_theme_builder 0.3.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +62 -9
- data/generators/%type%.liquid.tt +3 -0
- data/generators/comment.txt +1 -0
- data/generators/controller.js.tt +5 -0
- data/generators/doc.txt.tt +7 -0
- data/generators/index.js.tt +1 -0
- data/generators/schema.json.tt +33 -0
- data/generators/style.css.tt +3 -0
- data/lib/shopify_theme_builder/builder.rb +2 -2
- data/lib/shopify_theme_builder/command_line.rb +172 -1
- data/lib/shopify_theme_builder/filewatcher.rb +26 -0
- data/lib/shopify_theme_builder/liquid_processor.rb +32 -7
- data/lib/shopify_theme_builder/version.rb +1 -1
- data/lib/shopify_theme_builder/watcher.rb +115 -14
- data/lib/shopify_theme_builder.rb +4 -59
- metadata +24 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7d2c29efc227a315501fc0e21d47b8cb268f8cbd7da2887754321e4ae02914b2
|
|
4
|
+
data.tar.gz: 51abf4c9808331d461b486325b2a29dc3804d2546befab0ab523530370f471d3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 06e8ea516c5e0fa0ea36769a21d7c4cfcfbd5c5cbd4c4cf1c62c60707e041e645e00a7e45fcbcdea0fb993f1eef34008682901934a9f6a62299084394ba252d0
|
|
7
|
+
data.tar.gz: 1df921934a3ba4cca6f783a4409e4480cba8adaba7363503a6fbec0faf528007f86e8a02a52ccf039f3992942f6766c0f8fd573ea5a81e6625e4d939fd3f3dd6
|
data/README.md
CHANGED
|
@@ -33,6 +33,11 @@ _components/
|
|
|
33
33
|
schema.json
|
|
34
34
|
style.css
|
|
35
35
|
index.js
|
|
36
|
+
controllers.js
|
|
37
|
+
assets/
|
|
38
|
+
tailwind-output.css
|
|
39
|
+
tailwind.css
|
|
40
|
+
controllers.js
|
|
36
41
|
blocks/
|
|
37
42
|
button.liquid
|
|
38
43
|
```
|
|
@@ -43,6 +48,34 @@ All files inside the `button` folder will be compiled into a single `button.liqu
|
|
|
43
48
|
|
|
44
49
|
ShopifyThemeBuilder also supports Tailwind CSS integration. You can specify an input CSS file that includes Tailwind directives and an output CSS file where the compiled styles will be saved. The watcher will automatically run the Tailwind build process whenever changes are detected in the components folder.
|
|
45
50
|
|
|
51
|
+
## Stimulus JS Support
|
|
52
|
+
|
|
53
|
+
ShopifyThemeBuilder supports Stimulus JS integration. You can specify an output JavaScript file where the compiled Stimulus controllers will be saved. The watcher will automatically compile the Stimulus controllers whenever changes are detected in the components folder.
|
|
54
|
+
|
|
55
|
+
### How to create Stimulus controllers
|
|
56
|
+
|
|
57
|
+
To create Stimulus controllers, create a `controller.js` file inside your component folder. For example:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
_components/
|
|
61
|
+
my_component/
|
|
62
|
+
controller.js
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The content of the `controller.js` file should follow the Stimulus controller structure. For example:
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
class HelloController extends Controller {
|
|
69
|
+
connect() {
|
|
70
|
+
console.log("My component controller connected")
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
Stimulus.register("hello", HelloController)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
When the watcher runs, it will compile all `controller.js` files from your components into a single JavaScript file that can be included in your Shopify theme.
|
|
78
|
+
|
|
46
79
|
## Installation
|
|
47
80
|
|
|
48
81
|
Install the gem and add to the application's Gemfile by executing:
|
|
@@ -51,6 +84,14 @@ Install the gem and add to the application's Gemfile by executing:
|
|
|
51
84
|
bundle add shopify_theme_builder --group "development"
|
|
52
85
|
```
|
|
53
86
|
|
|
87
|
+
Run the gem's install command:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
bundle exec theme-builder install
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
This will inject Tailwind CSS and Stimulus JS into your Shopify theme layout, and add an entry in the `Procfile.dev` to run the watcher if present.
|
|
94
|
+
|
|
54
95
|
## Usage
|
|
55
96
|
|
|
56
97
|
To watch for changes in the default components folder and build the theme, run:
|
|
@@ -61,9 +102,10 @@ bundle exec theme-builder watch
|
|
|
61
102
|
|
|
62
103
|
You can customize multiple options when running the watcher:
|
|
63
104
|
- `--folders`: Specify one or more folders to watch (default is `_components`).
|
|
64
|
-
- `--tailwind-input-file`: Specify the Tailwind CSS input file (default is
|
|
65
|
-
- `--tailwind-output-file`: Specify the Tailwind CSS output file (default is
|
|
105
|
+
- `--tailwind-input-file`: Specify the Tailwind CSS input file (default is `./assets/tailwind.css`).
|
|
106
|
+
- `--tailwind-output-file`: Specify the Tailwind CSS output file (default is `./assets/tailwind-output.css`).
|
|
66
107
|
- `--skip-tailwind`: Skip the Tailwind CSS build process (default is `false`).
|
|
108
|
+
- `--stimulus-output-file`: Specify the Stimulus JS output file (default is `./assets/controllers.js`).
|
|
67
109
|
|
|
68
110
|
If you need help with all available options, or how to set them, run:
|
|
69
111
|
|
|
@@ -71,14 +113,24 @@ If you need help with all available options, or how to set them, run:
|
|
|
71
113
|
bundle exec theme-builder help watch
|
|
72
114
|
```
|
|
73
115
|
|
|
74
|
-
|
|
116
|
+
### Component generator
|
|
75
117
|
|
|
76
|
-
|
|
118
|
+
This gem also provides a command to generate a new component with all the necessary files. To create a new component, run:
|
|
77
119
|
|
|
78
|
-
```
|
|
79
|
-
|
|
120
|
+
```bash
|
|
121
|
+
bundle exec theme-builder generate
|
|
80
122
|
```
|
|
81
123
|
|
|
124
|
+
You can customize the component type, name and folder by providing additional options:
|
|
125
|
+
- `--type`: Specify the component type (`section`, `block`, or `snippet`).
|
|
126
|
+
- `--name`: Specify the component name.
|
|
127
|
+
- `--folder`: Specify the components folder (default is `_components`).
|
|
128
|
+
|
|
129
|
+
## Tested With
|
|
130
|
+
|
|
131
|
+
- [Skeleton Theme](https://github.com/Shopify/skeleton-theme)
|
|
132
|
+
- [Horizon Theme](https://github.com/Shopify/horizon)
|
|
133
|
+
|
|
82
134
|
## Development
|
|
83
135
|
|
|
84
136
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
@@ -88,9 +140,10 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
|
88
140
|
## Next Steps
|
|
89
141
|
|
|
90
142
|
- [x] Run the tailwind build process automatically.
|
|
91
|
-
- [
|
|
92
|
-
- [
|
|
93
|
-
- [ ]
|
|
143
|
+
- [x] Add Stimulus JS support.
|
|
144
|
+
- [x] Create a command to build an example component with all the files.
|
|
145
|
+
- [ ] Investigate if it's possible to use import maps instead of a single file for Stimulus controllers.
|
|
146
|
+
- [ ] Decompile existing Shopify files into components structure (Is it really needed?).
|
|
94
147
|
|
|
95
148
|
## Contributing
|
|
96
149
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
this is a comment
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
console.log("Hello from the <%= name %> <%= type %>")
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "<%= name %>",
|
|
3
|
+
"tag": "<%= type == "section" ? "section" : "div" %>",
|
|
4
|
+
"class": "<%= name.parameterize %>",
|
|
5
|
+
"limit": 1,
|
|
6
|
+
"settings": [
|
|
7
|
+
{
|
|
8
|
+
"type": "text",
|
|
9
|
+
"id": "title",
|
|
10
|
+
"label": "Title"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"max_blocks": 5,
|
|
14
|
+
"blocks": [{ "type": "@theme" }, { "type": "@app" }],
|
|
15
|
+
"presets": [
|
|
16
|
+
{
|
|
17
|
+
"name": "<%= name %>",
|
|
18
|
+
"settings": {
|
|
19
|
+
"title": "<%= name %>"
|
|
20
|
+
},
|
|
21
|
+
"blocks": []
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"locales": {
|
|
25
|
+
"en": {
|
|
26
|
+
"title": "<%= name %>"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"enabled_on": {
|
|
30
|
+
"templates": ["*"],
|
|
31
|
+
"groups": ["*"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -12,8 +12,8 @@ module ShopifyThemeBuilder
|
|
|
12
12
|
def build
|
|
13
13
|
puts "Processing #{@files_to_process.count} files..."
|
|
14
14
|
|
|
15
|
-
@files_to_process.each do |file|
|
|
16
|
-
@processed_files << ShopifyThemeBuilder::LiquidProcessor.new(file).process
|
|
15
|
+
@files_to_process.each do |file, event|
|
|
16
|
+
@processed_files << ShopifyThemeBuilder::LiquidProcessor.new(file:, event:).process
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
puts "Built #{@processed_files.count} files." if @processed_files.any?
|
|
@@ -1,23 +1,194 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "active_support/core_ext/string/inflections"
|
|
3
4
|
require "thor"
|
|
4
5
|
|
|
5
6
|
module ShopifyThemeBuilder
|
|
6
7
|
# CommandLine class to handle CLI commands using Thor.
|
|
7
8
|
class CommandLine < Thor
|
|
9
|
+
include Thor::Actions
|
|
10
|
+
|
|
11
|
+
SUPPORTED_TYPES = %w[section block snippet].freeze
|
|
12
|
+
|
|
13
|
+
attr_reader :type, :name
|
|
14
|
+
|
|
15
|
+
def self.source_root
|
|
16
|
+
File.expand_path("../..", __dir__)
|
|
17
|
+
end
|
|
18
|
+
|
|
8
19
|
desc "watch", "Watch for changes in the components folder and build the theme accordingly"
|
|
9
20
|
method_option :folders, type: :array, default: ["_components"], desc: "Folders to watch for changes"
|
|
10
21
|
method_option :tailwind_input_file, type: :string, default: "./assets/tailwind.css", desc: "Tailwind CSS input file"
|
|
11
22
|
method_option :tailwind_output_file, type: :string, default: "./assets/tailwind-output.css",
|
|
12
23
|
desc: "Tailwind CSS output file"
|
|
13
24
|
method_option :skip_tailwind, type: :boolean, default: false, desc: "Skip Tailwind CSS processing"
|
|
25
|
+
method_option :stimulus_output_file, type: :string, default: "./assets/controllers.js",
|
|
26
|
+
desc: "Stimulus controllers output file"
|
|
14
27
|
def watch
|
|
15
28
|
ShopifyThemeBuilder.watch(
|
|
16
29
|
folders_to_watch: options[:folders],
|
|
17
30
|
tailwind_input_file: options[:tailwind_input_file],
|
|
18
31
|
tailwind_output_file: options[:tailwind_output_file],
|
|
19
|
-
skip_tailwind: options[:skip_tailwind]
|
|
32
|
+
skip_tailwind: options[:skip_tailwind],
|
|
33
|
+
stimulus_output_file: options[:stimulus_output_file]
|
|
20
34
|
)
|
|
21
35
|
end
|
|
36
|
+
|
|
37
|
+
desc "install", "Set up your Shopify theme with Tailwind CSS, Stimulus JS, and the file watcher"
|
|
38
|
+
def install
|
|
39
|
+
add_tailwind_to_theme
|
|
40
|
+
add_stimulus_to_theme
|
|
41
|
+
add_watcher_to_procfile
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
desc "generate", "Generate an example component structure"
|
|
45
|
+
method_option :type, type: :string, desc: "Type of component to generate ('section', 'block' or 'snippet')"
|
|
46
|
+
method_option :name, type: :string, desc: "Name of the component to generate"
|
|
47
|
+
method_option :folder, type: :string, desc: "Folder to generate the component in"
|
|
48
|
+
def generate
|
|
49
|
+
@type = options[:type]
|
|
50
|
+
@name = options[:name]
|
|
51
|
+
folder = options[:folder]
|
|
52
|
+
|
|
53
|
+
until ShopifyThemeBuilder::CommandLine::SUPPORTED_TYPES.include?(@type)
|
|
54
|
+
unless @type.nil?
|
|
55
|
+
say_error "Error: Unsupported type '#{@type}'.\
|
|
56
|
+
Supported types are: #{ShopifyThemeBuilder::CommandLine::SUPPORTED_TYPES.join(", ")}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
@type = ask(
|
|
60
|
+
"Enter component type (section, block or snippet):",
|
|
61
|
+
limited_to: ShopifyThemeBuilder::CommandLine::SUPPORTED_TYPES
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
@name = ask("Enter component name (E.g., Slideshow):") if @name.nil?
|
|
66
|
+
|
|
67
|
+
folder = ask("Enter folder to generate the component in:", default: "_components") if folder.nil?
|
|
68
|
+
|
|
69
|
+
directory "generators", "#{folder}/#{@name.parameterize(separator: "_")}", exclude_pattern:
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def exclude_pattern
|
|
75
|
+
return "doc.txt" unless @type == "snippet"
|
|
76
|
+
|
|
77
|
+
"schema.json"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def add_tailwind_to_theme
|
|
81
|
+
theme_file_path =
|
|
82
|
+
if File.exist?("snippets/stylesheets.liquid")
|
|
83
|
+
"snippets/stylesheets.liquid"
|
|
84
|
+
elsif File.exist?("layout/theme.liquid")
|
|
85
|
+
"layout/theme.liquid"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
unless theme_file_path
|
|
89
|
+
say_error "Error: Could not find a theme file to inject Tailwind CSS.", :red
|
|
90
|
+
return
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
theme_file = File.read(theme_file_path)
|
|
94
|
+
|
|
95
|
+
if theme_file.include?("tailwind-output.css")
|
|
96
|
+
say "Tailwind CSS already included in #{theme_file_path}. Skipping injection.", :blue
|
|
97
|
+
return
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
injection_tag = "{{ 'tailwind-output.css' | asset_url | stylesheet_tag }}"
|
|
101
|
+
|
|
102
|
+
if theme_file_path == "snippets/stylesheets.liquid"
|
|
103
|
+
add_tailwind_to_snippet(theme_file_path, theme_file, injection_tag)
|
|
104
|
+
else
|
|
105
|
+
add_tailwind_to_layout(theme_file_path, theme_file, injection_tag)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def add_tailwind_to_snippet(theme_file_path, theme_file, injection_tag)
|
|
110
|
+
File.write(theme_file_path, "#{theme_file.chomp}\n#{injection_tag}\n")
|
|
111
|
+
say "Injected Tailwind CSS tag into #{theme_file_path}.", :green
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def add_tailwind_to_layout(theme_file_path, theme_file, injection_tag)
|
|
115
|
+
stylesheet_tag_regex =
|
|
116
|
+
/(\{\{\s*['"][^'"]+['"]\s*\|\s*asset_url\s*\|\s*stylesheet_tag(?:\s*:\s*((?!\}\}).)*)?\s*\}\})/
|
|
117
|
+
if theme_file.match?(stylesheet_tag_regex)
|
|
118
|
+
updated_content = theme_file.sub(
|
|
119
|
+
stylesheet_tag_regex,
|
|
120
|
+
"\\1\n#{injection_tag}"
|
|
121
|
+
)
|
|
122
|
+
File.write(theme_file_path, updated_content)
|
|
123
|
+
say "Injected Tailwind CSS tag into #{theme_file_path}.", :green
|
|
124
|
+
else
|
|
125
|
+
say_error "Error: Could not find a way to inject Tailwind CSS. Please manually add the CSS tag.",
|
|
126
|
+
:red
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def add_stimulus_to_theme
|
|
131
|
+
theme_file_path =
|
|
132
|
+
if File.exist?("snippets/scripts.liquid")
|
|
133
|
+
"snippets/scripts.liquid"
|
|
134
|
+
elsif File.exist?("layout/theme.liquid")
|
|
135
|
+
"layout/theme.liquid"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
unless theme_file_path
|
|
139
|
+
say_error "Error: Could not find a theme file to inject Stimulus JS.", :red
|
|
140
|
+
return
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
theme_file = File.read(theme_file_path)
|
|
144
|
+
|
|
145
|
+
if theme_file.include?("controllers.js")
|
|
146
|
+
say "Stimulus JS already included in #{theme_file_path}. Skipping injection.", :blue
|
|
147
|
+
return
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
injection_tag = "<script type=\"module\" defer=\"defer\" src=\"{{ 'controllers.js' | asset_url }}\"></script>"
|
|
151
|
+
|
|
152
|
+
if theme_file_path == "snippets/scripts.liquid"
|
|
153
|
+
add_stimulus_to_snippet(theme_file_path, theme_file, injection_tag)
|
|
154
|
+
else
|
|
155
|
+
add_stimulus_to_layout(theme_file_path, theme_file, injection_tag)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def add_stimulus_to_snippet(theme_file_path, theme_file, injection_tag)
|
|
160
|
+
File.write(theme_file_path, "#{theme_file.chomp}\n#{injection_tag}\n")
|
|
161
|
+
say "Injected Stimulus JS tag into #{theme_file_path}.", :green
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def add_stimulus_to_layout(theme_file_path, theme_file, injection_tag)
|
|
165
|
+
script_tag_regex = %r{(\s*)</body>}
|
|
166
|
+
if theme_file.match?(script_tag_regex)
|
|
167
|
+
updated_content = theme_file.sub(
|
|
168
|
+
script_tag_regex,
|
|
169
|
+
"\\1\n#{injection_tag}\n</body>"
|
|
170
|
+
)
|
|
171
|
+
File.write(theme_file_path, updated_content)
|
|
172
|
+
say "Injected Stimulus JS tag into #{theme_file_path}.", :green
|
|
173
|
+
else
|
|
174
|
+
say_error "Error: Could not find a way to inject Stimulus JS. Please manually add the JS tag.",
|
|
175
|
+
:red
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def add_watcher_to_procfile
|
|
180
|
+
procfile_path = "Procfile.dev"
|
|
181
|
+
return unless File.exist?(procfile_path)
|
|
182
|
+
|
|
183
|
+
procfile_content = File.read(procfile_path)
|
|
184
|
+
|
|
185
|
+
if procfile_content.include?("theme-builder watch")
|
|
186
|
+
say "Watcher command already present in #{procfile_path}. Skipping addition.", :blue
|
|
187
|
+
return
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
File.write(procfile_path, "#{procfile_content.chomp}\ntheme-builder: bundle exec theme-builder watch\n")
|
|
191
|
+
say "Added watcher command to #{procfile_path}.", :green
|
|
192
|
+
end
|
|
22
193
|
end
|
|
23
194
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "filewatcher"
|
|
4
|
+
|
|
5
|
+
module ShopifyThemeBuilder
|
|
6
|
+
# It wraps the Filewatcher functionality to monitor file changes.
|
|
7
|
+
# It delegates method calls to the underlying Filewatcher instance.
|
|
8
|
+
# Check: https://github.com/filewatcher/filewatcher
|
|
9
|
+
class Filewatcher
|
|
10
|
+
def initialize(...)
|
|
11
|
+
@filewatcher = ::Filewatcher.new(...)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def method_missing(name, ...)
|
|
15
|
+
if @filewatcher.respond_to?(name)
|
|
16
|
+
@filewatcher.send(name, ...)
|
|
17
|
+
else
|
|
18
|
+
super
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def respond_to_missing?(name, include_private)
|
|
23
|
+
@filewatcher.respond_to?(name) || super
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "logger"
|
|
4
|
+
|
|
3
5
|
module ShopifyThemeBuilder
|
|
4
6
|
# LiquidProcessor is responsible for processing Liquid files
|
|
5
7
|
# by combining various related files (Liquid, schema, CSS, JS, doc, comment)
|
|
@@ -22,17 +24,21 @@ module ShopifyThemeBuilder
|
|
|
22
24
|
liquid: LIQUID_FILE_TYPES.map { |type| "#{type}.liquid" },
|
|
23
25
|
schema: "schema.json",
|
|
24
26
|
stylesheet: "style.css",
|
|
25
|
-
javascript: "index.js"
|
|
27
|
+
javascript: "index.js",
|
|
28
|
+
stimulus: "controller.js"
|
|
26
29
|
}.freeze
|
|
27
30
|
SUPPORTED_VALUES = SUPPORTED_FILES.values.flatten.freeze
|
|
28
31
|
|
|
29
|
-
def initialize(file)
|
|
30
|
-
@file = file
|
|
32
|
+
def initialize(file:, event:)
|
|
33
|
+
@file = file.gsub("#{Dir.pwd}/", "")
|
|
34
|
+
@event = event
|
|
31
35
|
@contents = +""
|
|
32
36
|
@logger = Logger.new($stdout)
|
|
33
37
|
end
|
|
34
38
|
|
|
35
39
|
def process
|
|
40
|
+
return if component_deleted?
|
|
41
|
+
|
|
36
42
|
return unless processable?
|
|
37
43
|
|
|
38
44
|
compile_content
|
|
@@ -44,9 +50,28 @@ module ShopifyThemeBuilder
|
|
|
44
50
|
|
|
45
51
|
private
|
|
46
52
|
|
|
53
|
+
# If a liquid file is deleted, we want to delete the compiled file as well,
|
|
54
|
+
# and skip further processing.
|
|
55
|
+
def component_deleted?
|
|
56
|
+
return false unless File.extname(@file) == ".liquid" && @event == :deleted
|
|
57
|
+
|
|
58
|
+
file_to_remove = compiled_filename(file_type: File.basename(@file, ".liquid"))
|
|
59
|
+
|
|
60
|
+
if File.exist?(file_to_remove)
|
|
61
|
+
File.delete(file_to_remove)
|
|
62
|
+
@logger.info "Deleted compiled file: #{file_to_remove}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
true
|
|
66
|
+
end
|
|
67
|
+
|
|
47
68
|
# Returns true if the file is processable, false otherwise.
|
|
48
69
|
def processable?
|
|
49
|
-
supported? && correct_liquid_file? && correct_filename?
|
|
70
|
+
!directory? && supported? && correct_liquid_file? && correct_filename?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def directory?
|
|
74
|
+
File.directory?(@file)
|
|
50
75
|
end
|
|
51
76
|
|
|
52
77
|
# Checks if the file is in the list of supported files.
|
|
@@ -110,7 +135,7 @@ module ShopifyThemeBuilder
|
|
|
110
135
|
|
|
111
136
|
# Returns the compiled filename based on the component name and liquid file type.
|
|
112
137
|
# Example: _folder_to_watch/button/section.liquid -> sections/button.liquid
|
|
113
|
-
def compiled_filename
|
|
138
|
+
def compiled_filename(file_type: liquid_file_type)
|
|
114
139
|
filename_arr = file_dir.split(File::SEPARATOR) # Split the directory path into an array.
|
|
115
140
|
filename_arr = filename_arr.drop(1) # Remove the base components folder from the path. E.g., _components
|
|
116
141
|
filename_arr -= LIQUID_FILE_TYPES # Remove liquid file types from the path. E.g., section, snippet, block
|
|
@@ -119,12 +144,12 @@ module ShopifyThemeBuilder
|
|
|
119
144
|
|
|
120
145
|
return nil if filename.empty?
|
|
121
146
|
|
|
122
|
-
"#{
|
|
147
|
+
"#{file_type}s/#{filename}.liquid"
|
|
123
148
|
end
|
|
124
149
|
|
|
125
150
|
# Compiles the content by aggregating various related files.
|
|
126
151
|
def compile_content
|
|
127
|
-
SUPPORTED_FILES.each_key do |key|
|
|
152
|
+
SUPPORTED_FILES.except(:stimulus).each_key do |key|
|
|
128
153
|
@contents << formatted_content(key)
|
|
129
154
|
end
|
|
130
155
|
end
|
|
@@ -1,27 +1,128 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "tailwindcss/ruby"
|
|
4
5
|
|
|
5
6
|
module ShopifyThemeBuilder
|
|
6
|
-
# Watcher
|
|
7
|
-
#
|
|
8
|
-
# It delegates method calls to the underlying Filewatcher instance.
|
|
9
|
-
# Check: https://github.com/filewatcher/filewatcher
|
|
7
|
+
# Watcher is responsible for monitoring specified folders for changes
|
|
8
|
+
# and triggering the build process for Shopify theme components.
|
|
10
9
|
class Watcher
|
|
11
|
-
def initialize(
|
|
12
|
-
|
|
10
|
+
def initialize(
|
|
11
|
+
folders_to_watch: ["_components"],
|
|
12
|
+
tailwind_input_file: "./assets/tailwind.css",
|
|
13
|
+
tailwind_output_file: "./assets/tailwind-output.css",
|
|
14
|
+
skip_tailwind: false,
|
|
15
|
+
stimulus_output_file: "./assets/controllers.js"
|
|
16
|
+
)
|
|
17
|
+
@folders_to_watch = folders_to_watch
|
|
18
|
+
@tailwind_input_file = tailwind_input_file
|
|
19
|
+
@tailwind_output_file = tailwind_output_file
|
|
20
|
+
@skip_tailwind = skip_tailwind
|
|
21
|
+
@stimulus_output_file = stimulus_output_file
|
|
22
|
+
@stimulus_controller_file = ShopifyThemeBuilder::LiquidProcessor::SUPPORTED_FILES[:stimulus]
|
|
13
23
|
end
|
|
14
24
|
|
|
15
|
-
def
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
def watch
|
|
26
|
+
create_folders
|
|
27
|
+
|
|
28
|
+
initial_build
|
|
29
|
+
|
|
30
|
+
create_tailwind_file
|
|
31
|
+
|
|
32
|
+
run_tailwind
|
|
33
|
+
|
|
34
|
+
run_stimulus
|
|
35
|
+
|
|
36
|
+
watch_folders
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def create_folders
|
|
42
|
+
puts "Creating necessary folders..."
|
|
43
|
+
|
|
44
|
+
FileUtils.mkdir_p(@folders_to_watch)
|
|
45
|
+
FileUtils.mkdir_p("sections")
|
|
46
|
+
FileUtils.mkdir_p("blocks")
|
|
47
|
+
FileUtils.mkdir_p("snippets")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def initial_build
|
|
51
|
+
puts "Doing an initial build..."
|
|
52
|
+
|
|
53
|
+
files_to_process = {}
|
|
54
|
+
|
|
55
|
+
@folders_to_watch.each do |folder|
|
|
56
|
+
Dir.glob("#{folder}/**/*.*").each do |file|
|
|
57
|
+
files_to_process[file] = :updated
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
Builder.new(files_to_process: files_to_process).build
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def watch_folders
|
|
65
|
+
puts "Watching for changes in '#{@folders_to_watch.join(", ")}' folders..."
|
|
66
|
+
|
|
67
|
+
Filewatcher.new(@folders_to_watch).watch do |changes|
|
|
68
|
+
Builder.new(files_to_process: changes).build
|
|
69
|
+
|
|
70
|
+
run_tailwind
|
|
71
|
+
|
|
72
|
+
run_stimulus if changes.keys.any? { |f| f.end_with?(@stimulus_controller_file) }
|
|
20
73
|
end
|
|
21
74
|
end
|
|
22
75
|
|
|
23
|
-
def
|
|
24
|
-
|
|
76
|
+
def run_tailwind
|
|
77
|
+
return if @skip_tailwind
|
|
78
|
+
|
|
79
|
+
puts "Running Tailwind CSS build..."
|
|
80
|
+
|
|
81
|
+
system("tailwindcss", "-i", @tailwind_input_file, "-o", @tailwind_output_file)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def create_tailwind_file
|
|
85
|
+
return if @skip_tailwind || File.exist?(@tailwind_input_file)
|
|
86
|
+
|
|
87
|
+
puts "Creating default Tailwind CSS input file at '#{@tailwind_input_file}'..."
|
|
88
|
+
|
|
89
|
+
FileUtils.mkdir_p(File.dirname(@tailwind_input_file))
|
|
90
|
+
File.write @tailwind_input_file, tailwind_base_config
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def tailwind_base_config
|
|
94
|
+
return '@import "tailwindcss";' if Gem::Version.new(Tailwindcss::Ruby::VERSION) >= Gem::Version.new("4.0.0")
|
|
95
|
+
|
|
96
|
+
system("tailwindcss", "init") unless File.exist?("tailwind.config.js")
|
|
97
|
+
|
|
98
|
+
<<~TAILWIND.strip
|
|
99
|
+
@tailwind base;
|
|
100
|
+
@tailwind components;
|
|
101
|
+
@tailwind utilities;
|
|
102
|
+
TAILWIND
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def run_stimulus
|
|
106
|
+
controllers_files = @folders_to_watch.map { Dir.glob("#{_1}/**/#{@stimulus_controller_file}") }.flatten
|
|
107
|
+
|
|
108
|
+
return if controllers_files.empty?
|
|
109
|
+
|
|
110
|
+
puts "Building Stimulus controllers..."
|
|
111
|
+
|
|
112
|
+
content = +base_stimulus_content
|
|
113
|
+
|
|
114
|
+
controllers_files.each do |file|
|
|
115
|
+
content << File.read(file)
|
|
116
|
+
content << "\n"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
FileUtils.mkdir_p(File.dirname(@stimulus_output_file))
|
|
120
|
+
File.write(@stimulus_output_file, content.strip)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def base_stimulus_content
|
|
124
|
+
"import { Application, Controller } from \"https://unpkg.com/@hotwired/stimulus/dist/stimulus.js\"\n\
|
|
125
|
+
window.Stimulus = Application.start()\n\n"
|
|
25
126
|
end
|
|
26
127
|
end
|
|
27
128
|
end
|
|
@@ -1,70 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "fileutils"
|
|
4
|
-
require "logger"
|
|
5
3
|
require_relative "shopify_theme_builder/version"
|
|
6
|
-
require_relative "shopify_theme_builder/
|
|
4
|
+
require_relative "shopify_theme_builder/filewatcher"
|
|
7
5
|
require_relative "shopify_theme_builder/liquid_processor"
|
|
8
6
|
require_relative "shopify_theme_builder/builder"
|
|
9
7
|
require_relative "shopify_theme_builder/command_line"
|
|
8
|
+
require_relative "shopify_theme_builder/watcher"
|
|
10
9
|
|
|
11
10
|
# The main module for ShopifyThemeBuilder.
|
|
12
11
|
module ShopifyThemeBuilder
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
folders_to_watch: ["_components"],
|
|
16
|
-
tailwind_input_file: "./assets/tailwind.css",
|
|
17
|
-
tailwind_output_file: "./assets/tailwind-output.css",
|
|
18
|
-
skip_tailwind: false
|
|
19
|
-
)
|
|
20
|
-
create_folders(folders_to_watch)
|
|
21
|
-
|
|
22
|
-
initial_build(folders_to_watch)
|
|
23
|
-
|
|
24
|
-
run_tailwind(tailwind_input_file, tailwind_output_file) unless skip_tailwind
|
|
25
|
-
|
|
26
|
-
watch_folders(folders_to_watch) do
|
|
27
|
-
run_tailwind(tailwind_input_file, tailwind_output_file) unless skip_tailwind
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
def create_folders(folders_to_watch)
|
|
34
|
-
puts "Creating necessary folders..."
|
|
35
|
-
|
|
36
|
-
FileUtils.mkdir_p(folders_to_watch)
|
|
37
|
-
FileUtils.mkdir_p("sections")
|
|
38
|
-
FileUtils.mkdir_p("blocks")
|
|
39
|
-
FileUtils.mkdir_p("snippets")
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def initial_build(folders_to_watch)
|
|
43
|
-
puts "Doing an initial build..."
|
|
44
|
-
|
|
45
|
-
folders_to_watch.each do |folder|
|
|
46
|
-
Builder.new(files_to_process: Dir.glob("#{folder}/**/*.*")).build
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def watch_folders(folders_to_watch)
|
|
51
|
-
puts "Watching for changes in '#{folders_to_watch.join(", ")}' folders..."
|
|
52
|
-
|
|
53
|
-
Watcher.new(folders_to_watch).watch do |changes|
|
|
54
|
-
changes.each_key do |filename|
|
|
55
|
-
relative_filename = filename.gsub("#{Dir.pwd}/", "")
|
|
56
|
-
|
|
57
|
-
Builder.new(files_to_process: [relative_filename]).build if relative_filename.start_with?(*folders_to_watch)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
yield if block_given?
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def run_tailwind(tailwind_input_file, tailwind_output_file)
|
|
65
|
-
puts "Running Tailwind CSS build..."
|
|
66
|
-
|
|
67
|
-
system("tailwindcss", "-i", tailwind_input_file, "-o", tailwind_output_file)
|
|
68
|
-
end
|
|
12
|
+
def self.watch(...)
|
|
13
|
+
Watcher.new(...).watch
|
|
69
14
|
end
|
|
70
15
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: shopify_theme_builder
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Massimiliano Lattanzio
|
|
@@ -11,33 +11,39 @@ cert_chain: []
|
|
|
11
11
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: activesupport
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '6.0'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '9.0'
|
|
20
23
|
type: :runtime
|
|
21
24
|
prerelease: false
|
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
26
|
requirements:
|
|
24
|
-
- - "
|
|
27
|
+
- - ">="
|
|
25
28
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
29
|
+
version: '6.0'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '9.0'
|
|
27
33
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
34
|
+
name: filewatcher
|
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
|
30
36
|
requirements:
|
|
31
37
|
- - "~>"
|
|
32
38
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '1
|
|
39
|
+
version: '2.1'
|
|
34
40
|
type: :runtime
|
|
35
41
|
prerelease: false
|
|
36
42
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
43
|
requirements:
|
|
38
44
|
- - "~>"
|
|
39
45
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '1
|
|
46
|
+
version: '2.1'
|
|
41
47
|
- !ruby/object:Gem::Dependency
|
|
42
48
|
name: tailwindcss-ruby
|
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -84,9 +90,17 @@ files:
|
|
|
84
90
|
- README.md
|
|
85
91
|
- Rakefile
|
|
86
92
|
- exe/theme-builder
|
|
93
|
+
- generators/%type%.liquid.tt
|
|
94
|
+
- generators/comment.txt
|
|
95
|
+
- generators/controller.js.tt
|
|
96
|
+
- generators/doc.txt.tt
|
|
97
|
+
- generators/index.js.tt
|
|
98
|
+
- generators/schema.json.tt
|
|
99
|
+
- generators/style.css.tt
|
|
87
100
|
- lib/shopify_theme_builder.rb
|
|
88
101
|
- lib/shopify_theme_builder/builder.rb
|
|
89
102
|
- lib/shopify_theme_builder/command_line.rb
|
|
103
|
+
- lib/shopify_theme_builder/filewatcher.rb
|
|
90
104
|
- lib/shopify_theme_builder/liquid_processor.rb
|
|
91
105
|
- lib/shopify_theme_builder/version.rb
|
|
92
106
|
- lib/shopify_theme_builder/watcher.rb
|
|
@@ -115,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
115
129
|
- !ruby/object:Gem::Version
|
|
116
130
|
version: '0'
|
|
117
131
|
requirements: []
|
|
118
|
-
rubygems_version: 3.
|
|
132
|
+
rubygems_version: 3.6.9
|
|
119
133
|
specification_version: 4
|
|
120
134
|
summary: An opinionated builder for Shopify themes using nested folders, Tailwind
|
|
121
135
|
CSS, and Stimulus.
|