├── .gitattributes
├── .gitignore
├── .npminclude
├── Gruntfile.js
├── LICENSE
├── README.md
├── docs
├── options.md
├── process.md
├── remove.md
├── reuse.md
├── scripts_styles_link.md
└── sections.md
├── example.html
├── fixtures
├── css
│ ├── another.less
│ ├── dev.css
│ ├── example.inline.css
│ ├── index.inline.css
│ ├── inline.css
│ └── libs.css
├── scripts
│ ├── app.js
│ ├── libs.js
│ └── main.js
└── views
│ ├── view-recursive.html
│ ├── view1.html
│ ├── view2.html
│ └── view3.html
├── index.html
├── package.json
├── samples
├── example.html
└── index.html
└── tasks
└── build-html.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #################
2 | ## Eclipse
3 | #################
4 |
5 | *.pydevproject
6 | .project
7 | .metadata
8 | bin/
9 | tmp/
10 | *.tmp
11 | *.bak
12 | *.swp
13 | *~.nib
14 | local.properties
15 | .classpath
16 | .settings/
17 | .loadpath
18 |
19 | # External tool builders
20 | .externalToolBuilders/
21 |
22 | # Locally stored "Eclipse launch configurations"
23 | *.launch
24 |
25 | # CDT-specific
26 | .cproject
27 |
28 | # PDT-specific
29 | .buildpath
30 |
31 |
32 | #################
33 | ## Visual Studio
34 | #################
35 |
36 | ## Ignore Visual Studio temporary files, build results, and
37 | ## files generated by popular Visual Studio add-ons.
38 |
39 | # User-specific files
40 | .vs/
41 | *.suo
42 | *.user
43 | *.sln.docstates
44 | *.sln
45 | *.*proj
46 |
47 | # Build results
48 | [Dd]ebug/
49 | [Rr]elease/
50 | *_i.c
51 | *_p.c
52 | *.ilk
53 | *.meta
54 | *.obj
55 | *.pch
56 | *.pdb
57 | *.pgc
58 | *.pgd
59 | *.rsp
60 | *.sbr
61 | *.tlb
62 | *.tli
63 | *.tlh
64 | *.tmp
65 | *.vspscc
66 | .builds
67 | *.dotCover
68 |
69 | # Projects files
70 | *.sln
71 | *.csproj
72 |
73 | ## TODO: If you have NuGet Package Restore enabled, uncomment this
74 | packages/
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opensdf
81 | *.sdf
82 |
83 | # Visual Studio profiler
84 | *.psess
85 | *.vsp
86 |
87 | # ReSharper is a .NET coding add-in
88 | _ReSharper*
89 |
90 | # Installshield output folder
91 | [Ee]xpress
92 |
93 | # DocProject is a documentation generator add-in
94 | DocProject/buildhelp/
95 | DocProject/Help/*.HxT
96 | DocProject/Help/*.HxC
97 | DocProject/Help/*.hhc
98 | DocProject/Help/*.hhk
99 | DocProject/Help/*.hhp
100 | DocProject/Help/Html2
101 | DocProject/Help/html
102 |
103 | # Click-Once directory
104 | publish
105 |
106 | # Others
107 | [Bb]in
108 | [Oo]bj
109 | sql
110 | TestResults
111 | *.Cache
112 | ClientBin
113 | stylecop.*
114 | ~$*
115 | *.dbmdl
116 | Generated_Code #added for RIA/Silverlight projects
117 |
118 | # Backup & report files from converting an old project file to a newer
119 | # Visual Studio version. Backup files are not needed, because we have git ;-)
120 | _UpgradeReport_Files/
121 | Backup*/
122 | UpgradeLog*.XML
123 |
124 | # NodeJS
125 | node_modules
126 | npm-debug.log
127 |
128 | ############
129 | ## Windows
130 | ############
131 |
132 | # Windows image file caches
133 | Thumbs.db
134 |
135 | # Folder config file
136 | Desktop.ini
137 |
138 |
139 | #############
140 | ## Python
141 | #############
142 |
143 | *.py[co]
144 |
145 | # Packages
146 | *.egg
147 | *.egg-info
148 | dist
149 | build
150 | eggs
151 | parts
152 | bin
153 | var
154 | sdist
155 | develop-eggs
156 | .installed.cfg
157 |
158 | # Installer logs
159 | pip-log.txt
160 |
161 | # Unit test / coverage reports
162 | .coverage
163 | .tox
164 |
165 | #Translations
166 | *.mo
167 |
168 | #Mr Developer
169 | .mr.developer.cfg
170 |
171 | # Mac crap
172 | .DS_Store
173 |
--------------------------------------------------------------------------------
/.npminclude:
--------------------------------------------------------------------------------
1 | package.json
2 | tasks/build-html.js
3 | LICENSE
4 | README.md
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 | grunt.initConfig({
3 | fixturesPath: "fixtures",
4 |
5 | htmlbuild: {
6 | dist: {
7 | src: './*.html',
8 | dest: './samples/',
9 | options: {
10 | beautify: true,
11 | //allowUnknownTags: true,
12 | //parseTag: 'htmlbuild',
13 | // keepTags: true,
14 | relative: true,
15 | processFiles: true,
16 | scripts: {
17 | bundle: [
18 | '<%= fixturesPath %>/scripts/*.js',
19 | '!**/main.js',
20 | ],
21 | bundle_remote: [
22 | "//cdn.jsdelivr.net/jquery/2.1.0/jquery.min.js",
23 | "//cdn.jsdelivr.net/bootstrap/3.1.1/js/bootstrap.min.js"
24 | ],
25 | inlineapp: '<%= fixturesPath %>/scripts/app.js',
26 | main: '<%= fixturesPath %>/scripts/main.js'
27 | },
28 | styles: {
29 | bundle: {
30 | cwd: '<%= fixturesPath %>',
31 | files: [
32 | 'css/libs.css',
33 | 'css/dev.css',
34 | 'css/another.less'
35 | ]
36 | },
37 | test: '<%= fixturesPath %>/css/inline.css',
38 | pageSpecific: '<%= fixturesPath %>/css/$(filename).inline.css'
39 | },
40 | sections: {
41 | views: '<%= fixturesPath %>/views/**/*.html',
42 | templates: '<%= fixturesPath %>/templates/**/*.html',
43 | },
44 | data: {
45 | version: "0.1.0",
46 | title: "test",
47 | },
48 | }
49 | }
50 | }
51 | });
52 |
53 | grunt.loadTasks('tasks');
54 |
55 | grunt.registerTask('default', ['htmlbuild']);
56 | };
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 SPA Tools
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without restriction,
6 | including without limitation the rights to use, copy, modify, merge,
7 | publish, distribute, sublicense, and/or sell copies of the Software,
8 | and to permit persons to whom the Software is furnished to do so,
9 | subject to 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
16 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
18 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # grunt-html-build [](http://badge.fury.io/js/grunt-html-build)
2 |
3 | [Grunt][grunt] HTML Builder - Appends scripts and styles, Removes debug parts, append html partials, Template options
4 |
5 | ## Getting Started
6 |
7 | Install this grunt plugin next to your project's gruntfile with: `npm install grunt-html-build --save-dev`
8 |
9 | Then add this line to your project's `Gruntfile.js` :
10 |
11 | ```javascript
12 | grunt.loadNpmTasks('grunt-html-build');
13 | ```
14 |
15 | Then specify your config: ([more informations][doc-options])
16 |
17 | ```javascript
18 | grunt.initConfig({
19 | fixturesPath: "fixtures",
20 |
21 | htmlbuild: {
22 | dist: {
23 | src: 'index.html',
24 | dest: 'samples/',
25 | options: {
26 | beautify: true,
27 | prefix: '//some-cdn',
28 | relative: true,
29 | basePath: false,
30 | scripts: {
31 | bundle: [
32 | '<%= fixturesPath %>/scripts/*.js',
33 | '!**/main.js',
34 | ],
35 | main: '<%= fixturesPath %>/scripts/main.js'
36 | },
37 | styles: {
38 | bundle: [
39 | '<%= fixturesPath %>/css/libs.css',
40 | '<%= fixturesPath %>/css/dev.css'
41 | ],
42 | test: '<%= fixturesPath %>/css/inline.css'
43 | },
44 | sections: {
45 | views: '<%= fixturesPath %>/views/**/*.html',
46 | templates: '<%= fixturesPath %>/templates/**/*.html',
47 | layout: {
48 | header: '<%= fixturesPath %>/layout/header.html',
49 | footer: '<%= fixturesPath %>/layout/footer.html'
50 | }
51 | },
52 | data: {
53 | // Data to pass to templates
54 | version: "0.1.0",
55 | title: "test",
56 | },
57 | }
58 | }
59 | }
60 | });
61 | ```
62 |
63 | Using the configuration above, consider the following example html to see it in action:
64 |
65 | ```html
66 |
67 |
68 | grunt-html-build - Test Page
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
106 |
107 |
108 |
111 |
112 |
113 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | ```
123 |
124 | After running the grunt task it will be stored on the samples folder as
125 |
126 | ```html
127 |
128 |
129 | grunt-html-build - Test Page
130 |
131 |
132 |
137 |
138 |
139 |
140 | ...
141 | ...
142 | ...
143 |
144 |
145 |
146 |
147 |
151 |
152 |
156 |
160 |
161 |
162 | ```
163 |
164 | There 5 types of processors:
165 |
166 | * [script][doc-scripts-styles]
167 | * append script reference from configuration to dest file.
168 | * [style][doc-scripts-styles]
169 | * append style reference from configuration to dest file.
170 | * [section][doc-sections]
171 | * append partials from configuration to dest file.
172 | * [process][doc-process]
173 | * process grunt template on the block.
174 | * [remove][doc-remove]
175 | * it will erase the whole block.
176 |
177 | [grunt]: https://github.com/gruntjs/grunt
178 | [doc-options]: https://github.com/spatools/grunt-html-build/wiki/Task-Options
179 | [doc-scripts-styles]: https://github.com/spatools/grunt-html-build/wiki/Linking-Scripts-and-Styles
180 | [doc-sections]: https://github.com/spatools/grunt-html-build/wiki/Creating-HTML-Sections
181 | [doc-process]: https://github.com/spatools/grunt-html-build/wiki/Using-HTML-as-Template
182 | [doc-remove]: https://github.com/spatools/grunt-html-build/wiki/Removing-parts
183 | [doc-reuse]: https://github.com/spatools/grunt-html-build/wiki/Creating-reusable-HTML-Layout-Template
184 |
185 | ## Release History
186 | * 0.1.0 Initial Release
187 | * 0.1.1 Cleaning, adding optional tags, using js-beautify
188 | * 0.1.2 Adding expand options to tags paths and write docs
189 | * 0.1.3 Fixing nodejs dependencies
190 | * 0.1.4 Fixing nodejs dependencies
191 | * 0.1.5 Optimize src loop / Fix js-beautify options
192 | * 0.1.6 Allow build tag customization
193 | * 0.2.0
194 | * Fix and optimisation
195 | * Allow replacing src file by built file
196 | * Allow filename in dest path
197 | * Allow prefixing src files
198 | * 0.2.1 Allow non relative file names + per file tag parameter
199 | * 0.2.2 Fix issue in options.relative
200 | * 0.3.0
201 | * Fix issue when building multiple html files using custom file globbing
202 | * Allow sub parameters in all options paths
203 | * 0.3.1
204 | * Fix issue when using prefix on Windows environment
205 | * 0.3.2
206 | * Update js-beautify dependency to 1.4.2
207 | * Remove peerDependencies to avoid versions conflict
208 | * 0.4.0
209 | * Fix bug in prefix option
210 | * Allow recursive build using section processor
211 | * 0.4.1
212 | * Fix prefix's ability to contain //, as in https://
213 | * Add a new recursive option which can be added to section tag.
214 | * 0.4.2
215 | * Fix relative path error since 0.4.0
216 | * Add noprocess option to tags to avoid underscore processing issues.
217 | * 0.4.3
218 | * Allow remove task to be configured by using current target.
219 | * 0.5.0
220 | * Avoid javascript errors when parsing unknown tags.
221 | * Add an `allowUnknownTags` option to ignore unknown tags without failing the task.
222 | * Allow to specify `attributes` on script and styles tags.
223 | * Allow http, https or // links to be processed as links.
224 | * Automatically adapt generated `link` tag for less files.
225 | * 0.5.1
226 | * Fix issue in linefeed simplification
227 | * 0.5.2
228 | * Fix issue in relative path management
229 | * Remove deprecated undercore reference and replace by lodash dependency
230 | * Fix deep array flattening for Node.JS >= 4
231 | * 0.6.0
232 | * Add `keepTags` option to keep htmlbuild tags after build.
233 | * Fix broken inlining for scripts containing `$x`.
234 | * Upgrade `lodash` to v4 and fix template issue.
235 | * Ensure `grunt` v1.0.0 compatibility.
236 | * 0.6.1
237 | * Optimize some code parts.
238 | * Added option `basePath` allow keeping original folder structure.
239 | * 0.6.2
240 | * Fix some issues.
241 | * 0.7.0
242 | * Fix EOL inconsistent issue.
243 | * Add suffix option to customize files query parameters (for cache).
244 |
--------------------------------------------------------------------------------
/docs/options.md:
--------------------------------------------------------------------------------
1 | # grunt-html-build
2 |
3 | ## Task Options
4 |
5 | grunt-html-build is a multi task grunt plugin, so it's possible to configure differents targets within htmlbuild tag.
6 |
7 | ### Example
8 |
9 | ```javascript
10 | grunt.initConfig({
11 | htmlbuild: {
12 | dist: {
13 | src: 'index.html',
14 | dest: 'samples/',
15 | options: {
16 | beautify: true,
17 | scripts: {
18 | bundle: [
19 | 'scripts/*.js',
20 | '!**/main.js',
21 | ],
22 | main: 'scripts/main.js'
23 | },
24 | styles: {
25 | bundle: [
26 | 'css/libs.css',
27 | 'css/dev.css'
28 | ],
29 | test: 'css/inline.css'
30 | },
31 | sections: {
32 | views: 'views/**/*.html',
33 | templates: 'templates/**/*.html',
34 | },
35 | data: {
36 | // Data to pass to templates
37 | version: "0.1.0",
38 | title: "test",
39 | },
40 | }
41 | }
42 | }
43 | });
44 | ```
45 |
46 | ### src
47 | **type:** string, array |
48 | **required**
49 |
50 | Specify input files to build.
51 | Accept globbing patterns
52 |
53 | ### dest
54 | **type:** string |
55 | **optional** |
56 | **default:** './'
57 |
58 | Specify output directory to create results in
59 |
60 | ### options
61 | **type:** object |
62 | **required**
63 |
64 | Additional Options
65 |
66 | ### options.scripts, options.styles, options.sections
67 | **type:** object |
68 | **required if there is scripts or styles or sections tags in html**
69 |
70 | Object representing files to be inserted into html :
71 |
72 | #### Examples:
73 |
74 | ##### Simple configuration
75 |
76 | ```javascript
77 | scripts: {
78 | 'name1': '/path/to/file/with/**/globbing.*.ext'
79 | 'name2': [
80 | '/path/to/file/with/**/globbing.*.ext',
81 | '!/path/to/file/to/exclude/globbing.*.ext'
82 | ]
83 | }
84 | ```
85 | ```html
86 |
87 |
88 |
89 |
90 |
91 | ```
92 |
93 | ##### Advanced configuration
94 |
95 | ```javascript
96 | scripts: {
97 | 'name1': {
98 | nocase: true,
99 | files: '/path/to/file/with/**/globbing.*.ext'
100 | },
101 | 'name2': {
102 | cwd: '/path/to/file',
103 | files: [
104 | 'with/**/globbing.*.ext',
105 | '!to/exclude/globbing.*.ext'
106 | ]
107 | }
108 | }
109 | ```
110 | ```html
111 |
112 |
113 |
114 |
115 |
116 | ```
117 |
118 | ### options.prefix
119 | **type :** string |
120 | **optional** |
121 | **default:** null
122 |
123 | Append this prefix to all paths in script and style references.
124 |
125 | ### options.suffix
126 | **type :** string, function |
127 | **optional** |
128 | **default:** null
129 |
130 | Append this suffix to to CSS and JS files. Could be a `function(filename, url)` which return a string. The result will be appended to the URL in the form `{url}?{suffix}`
131 |
132 | ### options.relative
133 | **type :** string |
134 | **optional** |
135 | **default:** true
136 |
137 | Make generated path relative to dest path. If this arguments is specified with false value, generated paths will be written as you configure in your Gruntfile.
138 |
139 | ### options.replace
140 | **type :** bool |
141 | **optional** |
142 | **default:** false
143 |
144 | True to replace src file instead of creating a new file.
145 |
146 | ### options.keepTags
147 | **type:** boolean |
148 | **optional** |
149 | **default:** false
150 |
151 | True to keep `htmlbuild` special tags after HTML compilation.
152 |
153 | ### options.beautify
154 | **type :** bool |
155 | **optional** |
156 | **default:** false
157 |
158 | True to beautify HTML result
159 |
160 | ### options.basePath
161 |
162 | **type :** string |
163 | **optional** |
164 | **default:** false
165 |
166 | Set to copy the whole folder structure.
167 |
168 | ```javascript
169 | grunt.initConfig({
170 | htmlbuild: {
171 | dev: { // compile with dev options
172 | src: 'source/app/**/*.html',
173 | dest: 'tmp/',
174 | options: {
175 | basePath: 'source/',
176 | // destination path = dest + ( src with 'basePath' cut away)
177 | // sourcefile: 'source/app/customers/customer.html'
178 | // dest = 'tmp/' + 'app/customers/customer.html'
179 | // dest = 'tmp/app/customers/customer.html'
180 | }
181 | }
182 | }
183 | )};
184 | ```
185 |
186 | ### options.logOptionals
187 | **type :** bool |
188 | **optional** |
189 | **default:** false
190 |
191 | Log an alert in console if some optional tags are not rendered
192 |
193 | ### options.allowUnknownTags
194 | **type :** bool |
195 | **optional** |
196 | **default:** false
197 |
198 | Do not fail the task if the parser meet unknown tags.
199 | Useful when working with `grunt-usemin`.
200 |
201 | ### options.parseTag
202 | **type:** string |
203 | **optional** |
204 | **default:** 'build'
205 |
206 | Specify the html-build tag name, default is 'build'.
207 | Format :
208 |
209 | ### options.EOL
210 | **type:** string |
211 | **optional** |
212 | **default:** *autodectect*
213 |
214 | Force output EOL. If not specified, it will be detected from the input file.
215 |
216 |
217 | ### options.processFiles
218 |
219 | **type :** boolean |
220 | **optional** |
221 | **default:** false
222 |
223 | Set to true to enable files src configuration replacement.
224 | By enabling it, the following keywords will be replaced in src for each file processed during build:
225 | * `$(filename)`: Current filename (without extension).
226 | * `$(file)`: Current filename (with extension).
227 | * `$(dirname)`: Current directory name.
228 | * `$(path)`: Current file path.
229 | * `$(dir)`: Current directory path.
230 | * `$(platform)`: The result of `process.platform`.
--------------------------------------------------------------------------------
/docs/process.md:
--------------------------------------------------------------------------------
1 | # grunt-html-build
2 |
3 | ## Using HTML as Template
4 |
5 | When using grunt-html-build, you can make some parts of HTML files to be processed as grunt template.
6 | You can use values from config and from htmlbuild's option's data as globals.
7 |
8 | ### index.html
9 |
10 | ```html
11 |
12 |
13 |
14 | <%= pkg.name %>
15 |
16 | ...
17 |
18 |
19 | No process here
20 | <%= noprocess %>
21 |
22 |
26 |
27 |
28 |
29 | ```
30 |
31 | ### Gruntfile.js
32 |
33 | ```javascript
34 | grunt.initConfig({
35 | pkg: grunt.file.readJSON("package.json"),
36 |
37 | htmlbuild: {
38 | dist: {
39 | src: 'index.html',
40 | dest: 'dist/',
41 | options: {
42 | data: {
43 | baseUrl: "http://my.prod.site.com/"
44 | }
45 | }
46 | }
47 | }
48 | });
49 | ```
50 |
51 | ### Result
52 |
53 | ```html
54 |
55 |
56 | YourPackageName
57 | ...
58 |
59 |
60 | No process here
61 | <%= noprocess %>
62 |
63 |
67 |
68 |
69 |
70 | ```
71 |
--------------------------------------------------------------------------------
/docs/remove.md:
--------------------------------------------------------------------------------
1 | # grunt-html-build
2 |
3 | ## Removing parts
4 |
5 | When using grunt-html-build, you can generate a HTML page which remove some dev parts.
6 |
7 | ### index.html
8 |
9 | ```html
10 |
11 |
12 | ...
13 |
14 |
15 | ...
16 | ...
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | ```
27 |
28 | ### Gruntfile.js
29 |
30 | ```javascript
31 | grunt.initConfig({
32 | htmlbuild: {
33 | dist: {
34 | src: 'index.html',
35 | dest: 'dist/'
36 | }
37 | }
38 | });
39 | ```
40 |
41 | ### Result
42 |
43 | ```html
44 |
45 |
46 | ...
47 |
48 |
49 | ...
50 | ...
51 |
52 |
53 |
54 | ```
55 |
56 | ### Per target
57 |
58 | Note that __.prod-only__ is part is kept because we configured __remove__ task to remove this part only during __dev__ or __test__ target.
59 |
--------------------------------------------------------------------------------
/docs/reuse.md:
--------------------------------------------------------------------------------
1 | # grunt-html-build
2 |
3 | ## Creating reusable HTML Layout Template
4 |
5 | When using grunt-html-build, you can reuse same HTML layout template between multiple projects.
6 | But when creating reusable HTML layout template, you must take care of some things :
7 |
8 | 1. Think of features you need en each project
9 | * Custom Title
10 | * Styles
11 | * Scripts
12 | * Views
13 | * Templates
14 | 2. Use optional attribute on optional tags
15 | * Prevent grunt from fail when no match is found for a tag in the config
16 | * Use it precautially, it prevents some errors, in a project, some parts are require, some are optional
17 | 3. Think reuse
18 | * Create some optional sections to help customize layout between projects
19 |
20 | ### index.html
21 |
22 | ```html
23 |
24 |
25 |
26 | <%= pkg.name %>
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | ```
47 |
--------------------------------------------------------------------------------
/docs/scripts_styles_link.md:
--------------------------------------------------------------------------------
1 | # grunt-html-build
2 |
3 | ## Linking Scripts and Styles
4 |
5 | When using grunt-html-build, you can generate a HTML page which replace debug scripts and style links by production ones.
6 |
7 | Let's take an application directory like this :
8 |
9 | * app
10 | * module1.js
11 | * module2.js
12 | * libs
13 | * lib1.js
14 | * lib2.js
15 | * css
16 | * lib1.css
17 | * lib2.css
18 | * app.css
19 | * main.js
20 | * index.html
21 |
22 |
23 | ### index.html
24 |
25 | ```html
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
51 |
52 |
53 |
56 |
57 |
58 |
59 |
60 | ```
61 |
62 | ### Gruntfile.js
63 |
64 | ```javascript
65 | grunt.initConfig({
66 | htmlbuild: {
67 | src: 'index.html',
68 | dest: 'dist/',
69 | options: {
70 | scripts: {
71 | libs: 'libs/*.js',
72 | app: 'app/*.js',
73 | main: 'main.js'
74 | },
75 | styles: {
76 | libs: [
77 | 'css/lib1.css',
78 | 'css/lib2.css'
79 | ],
80 | app: 'css/app.css'
81 | }
82 | }
83 | }
84 | });
85 | ```
86 |
87 | ### Result
88 |
89 | After grunt build, created index.html will contains links to files specified in options.
90 |
91 | ```html
92 |
93 |
94 |
95 |
96 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
109 |
114 |
115 |
116 | ```
117 |
118 | ### Tag options
119 |
120 | * __optional__: Specifies that the tag can be omited from configuration. If not specified and no configuration exists for this particular tag. The task will fail.
121 | * __inline__: Specifies that the tag must render the content of files directly in the resulting HTML.
122 | * __noprocess__: Specifies that the tag content must not be processed by grunt.js templating engine. Must be used with __inline__.
123 | * __[attributes]__: Specifies attributes that will be added to the resultings tags.
124 |
--------------------------------------------------------------------------------
/docs/sections.md:
--------------------------------------------------------------------------------
1 | # grunt-html-build
2 |
3 | ## Creating HTML Sections
4 |
5 | When using grunt-html-build, you can generate a HTML page using many input files.
6 |
7 | Let's take an application directory like this :
8 |
9 | * views
10 | * view1.html
11 | * view2.html
12 | * templates
13 | * tmpl1.html
14 | * tmpl2.html
15 | * index.html
16 |
17 |
18 | ### index.html
19 |
20 | ```html
21 |
22 |
23 | ...
24 |
25 |
26 |
27 |
28 |
29 |
30 | ```
31 |
32 | ### Gruntfile.js
33 |
34 | ```javascript
35 | grunt.initConfig({
36 | htmlbuild: {
37 | src: 'index.html',
38 | dest: 'dist/',
39 | options: {
40 | sections: {
41 | views: 'views/**/*.html',
42 | templates: 'templates/**/*.html',
43 | }
44 | }
45 | }
46 | });
47 | ```
48 |
49 | After grunt build process the index.html file will contain files in views and template directories.
50 |
51 | ### Recursive option
52 |
53 | If the __recursive__ option is specified in the tag, __grunt-html-build__ will recursively build every views passed in the section.
54 |
55 | Useful to create a main layout file and cut your work in little files.
56 |
--------------------------------------------------------------------------------
/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Page Specific Inline Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/fixtures/css/another.less:
--------------------------------------------------------------------------------
1 | .this-is-less {
2 |
3 | }
--------------------------------------------------------------------------------
/fixtures/css/dev.css:
--------------------------------------------------------------------------------
1 | .this-is-not-inline {
2 |
3 | }
--------------------------------------------------------------------------------
/fixtures/css/example.inline.css:
--------------------------------------------------------------------------------
1 | .this-is-example-inline {
2 | font-style: italic;
3 | }
--------------------------------------------------------------------------------
/fixtures/css/index.inline.css:
--------------------------------------------------------------------------------
1 | .this-is-index-inline {
2 | background: red;
3 | }
--------------------------------------------------------------------------------
/fixtures/css/inline.css:
--------------------------------------------------------------------------------
1 | .this-is-inline {
2 | font-weight: bold;
3 | }
--------------------------------------------------------------------------------
/fixtures/css/libs.css:
--------------------------------------------------------------------------------
1 | .this-is-not-inline {
2 |
3 | }
--------------------------------------------------------------------------------
/fixtures/scripts/app.js:
--------------------------------------------------------------------------------
1 | var version = "<%= version %>";
2 |
--------------------------------------------------------------------------------
/fixtures/scripts/libs.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/fixtures/scripts/main.js:
--------------------------------------------------------------------------------
1 | productionMain();
--------------------------------------------------------------------------------
/fixtures/views/view-recursive.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
<%= title %> - <%= version %>
4 |
5 |
6 |
--------------------------------------------------------------------------------
/fixtures/views/view1.html:
--------------------------------------------------------------------------------
1 | ...
2 |
--------------------------------------------------------------------------------
/fixtures/views/view2.html:
--------------------------------------------------------------------------------
1 | ...
2 |
--------------------------------------------------------------------------------
/fixtures/views/view3.html:
--------------------------------------------------------------------------------
1 | ...
2 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | grunt-html-build - Test Page
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "grunt-html-build",
3 | "description": "Grunt HTML Builder - Appends scripts and styles, Removes debug parts, append html partials, Template options",
4 | "version": "0.7.1",
5 | "homepage": "https://github.com/spatools/grunt-html-build.git",
6 | "license": "MIT",
7 | "author": {
8 | "name": "SPA Tools",
9 | "url": "http://github.com/spatools/"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git://github.com/spatools/grunt-html-build.git"
14 | },
15 | "bugs": {
16 | "url": "https://github.com/spatools/grunt-html-build/issues"
17 | },
18 | "scripts": {
19 | "test": "grunt --stack"
20 | },
21 | "devDependencies": {
22 | "grunt": "^1.0.1"
23 | },
24 | "dependencies": {
25 | "js-beautify": "^1.6.2",
26 | "lodash": "^4.12.0"
27 | },
28 | "keywords": [
29 | "gruntplugin",
30 | "grunt",
31 | "htmlbuild",
32 | "html",
33 | "build",
34 | "htmlgen",
35 | "generate",
36 | "link"
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/samples/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Page Specific Inline Example
7 |
8 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/samples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | grunt-html-build - Test Page
5 |
6 |
7 |
8 |
9 |
10 |
11 |
16 |
21 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | <%= title %> -
33 | <%= version %>
34 |
35 |
36 |
37 |
38 | ...
39 |
40 | ...
41 |
42 | ...
43 |
44 |
45 |
46 |
test - 0.1.0
47 |
48 |
49 | ...
50 | ...
51 | ...
52 |
53 |
54 |
55 |
56 |
57 |
60 |
63 |
64 |
68 |
69 |
70 |
71 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/tasks/build-html.js:
--------------------------------------------------------------------------------
1 | /*
2 | * grunt-html-build
3 | * https://github.com/spatools/grunt-html-build
4 | * Copyright (c) 2013 SPA Tools
5 | * Code below is licensed under MIT License
6 | *
7 | * Permission is hereby granted, free of charge, to any person
8 | * obtaining a copy of this software and associated documentation
9 | * files (the "Software"), to deal in the Software without restriction,
10 | * including without limitation the rights to use, copy, modify, merge,
11 | * publish, distribute, sublicense, and/or sell copies of the Software,
12 | * and to permit persons to whom the Software is furnished to do so,
13 | * subject to the following conditions:
14 | *
15 | * The above copyright notice and this permission notice shall be
16 | * included in all copies or substantial portions of the Software.
17 | *
18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
22 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
23 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 | */
26 |
27 | module.exports = function (grunt) {
28 | //#region Global Properties
29 |
30 | var // Init
31 | _ = require("lodash"),
32 | URL = require("url"),
33 | path = require("path"),
34 | beautifier = require("js-beautify"),
35 | beautify = {
36 | js: beautifier.js,
37 | css: beautifier.css,
38 | html: beautifier.html
39 | },
40 |
41 | // Tags Regular Expressions
42 | regexTagStartTemplate = "", // {} required () optional
43 | regexTagEndTemplate = "", //
44 | regexTagStart = "",
45 | regexTagEnd = "",
46 | isFileRegex = /\.(\w+){2,4}$/,
47 | processFileRegex = /\$\(([^\)]*)\)/;
48 |
49 | //#endregion
50 |
51 | //#region Private Methods
52 |
53 | function getBuildTags(content) {
54 | var lines = content.replace(/\r?\n/g, "\n").split(/\n/),
55 | tag = false,
56 | tags = [],
57 | last;
58 |
59 | lines.forEach(function (l) {
60 | var tagStart = l.match(new RegExp(regexTagStart)),
61 | tagEnd = new RegExp(regexTagEnd).test(l);
62 |
63 | if (tagStart) {
64 | tag = true;
65 | last = {
66 | type: tagStart[1],
67 | inline: !!tagStart[2],
68 | optional: !!tagStart[3],
69 | recursive: !!tagStart[4],
70 | noprocess: !!tagStart[5],
71 | name: tagStart[6],
72 | attributes: tagStart[7],
73 | lines: []
74 | };
75 | tags.push(last);
76 | }
77 |
78 | // switch back tag flag when endbuild
79 | if (tag && tagEnd) {
80 | last.lines.push(l);
81 | tag = false;
82 | }
83 |
84 | if (tag && last) {
85 | last.lines.push(l);
86 | }
87 | });
88 |
89 | return tags;
90 | }
91 | function defaultProcessPath(pathes, params, opt) { //takes an array of paths and validates them
92 | var local = grunt.file.expand(opt, pathes),
93 | flattenPaths = _.isArray(pathes) ? _.flattenDeep(pathes) : pathes,
94 | remote = flattenPaths.filter(function (path) { //for loading from cdn
95 | return /^((http|https):)?(\\|\/\/)/.test(path); //is http, https, or //
96 | });
97 |
98 | if (params.relative && opt.cwd) {
99 | local = local.map(function (src) { return path.join(opt.cwd, src); });
100 | }
101 |
102 | return _.uniq(local.concat(remote));
103 | }
104 | function validateBlockWithName(tag, params) {
105 | var src = params[tag.type + "s"],
106 |
107 | keys = tag.name.split("."),
108 | ln = keys.length;
109 |
110 | for (var i = 0; i < ln; i++) {
111 | src = src[keys[i]]; // Search target
112 | }
113 |
114 | if (src) {
115 | // check if we want to use current file name, if so update src where necessary
116 | if( params.useFileName && src.toString().match(/_CURRENT_FILE_NAME_/g) ) {
117 | src = src.toString().replace("_CURRENT_FILE_NAME_", processedFile);
118 | }
119 |
120 | var opt = {},
121 | files = src;
122 |
123 | if (_.isObject(src)) {
124 | if (src.files) {
125 | opt = _.omit(src, "files");
126 | files = src.files;
127 | }
128 | else {
129 | // if paths are named, just take values
130 | files = _.values(src);
131 | }
132 | }
133 |
134 | if (!Array.isArray(files)) {
135 | files = [files];
136 | }
137 |
138 | if (params.processFiles) {
139 | var filesContext = params.filesContext;
140 | files = files.map(function(f) {
141 | return f.replace(processFileRegex, function(val, name) {
142 | return filesContext[name] || val;
143 | });
144 | });
145 | }
146 |
147 | return params.processPath(files, params, opt);
148 | }
149 | }
150 | function validateBlockAlways(tag) {
151 | return true;
152 | }
153 |
154 | function setTagRegexes(parseTag) {
155 | regexTagStart = regexTagStartTemplate.replace(/%parseTag%/, function () { return parseTag });
156 | regexTagEnd = regexTagEndTemplate.replace(/%parseTag%/, function () { return parseTag });
157 | }
158 |
159 | function createFilesContext(src) {
160 | return {
161 | path: src,
162 | dir: path.dirname(src),
163 | file: path.basename(src),
164 | filename: path.basename(src, path.extname(src)),
165 | dirname: path.basename(path.dirname(src)),
166 | platform: process.platform
167 | };
168 | }
169 |
170 | //#endregion
171 |
172 | //#region Processors Methods
173 |
174 | function createTemplateData(options, src, attrs) {
175 | var extend = {
176 | src: src || "",
177 | attributes: attrs || ""
178 | };
179 |
180 | return {
181 | data: _.extend({}, options.data, extend)
182 | };
183 | }
184 | function processTemplate(template, options, src, attrs) {
185 | return grunt.template.process(template, createTemplateData(options, src, attrs));
186 | }
187 | function createAttributes(options, src) {
188 | var attrs = options.attributes || "";
189 |
190 | if (options.type === "script") {
191 | attrs = 'type="text/javascript" ' + attrs;
192 | }
193 | else if (options.type === "style" && !options.inline) {
194 | if (path.extname(src) === ".less") {
195 | attrs = 'type="text/css" rel="stylesheet/less" ' + attrs;
196 | }
197 | else {
198 | attrs = 'type="text/css" rel="stylesheet" ' + attrs;
199 | }
200 | }
201 |
202 | return attrs.trim();
203 | }
204 | function processHtmlTagTemplate(options, src) {
205 | var template = templates[options.type + (options.inline ? "-inline" : "")],
206 | attrs = createAttributes(options, src);
207 |
208 | if (!options.inline || options.noprocess) {
209 | return template
210 | .replace(/\<\%\= (src|attributes) \%\>/g, function (match, p1) {
211 | if (p1 === "src") {
212 | return src;
213 | }
214 | else if (p1 === "attributes") {
215 | return attrs;
216 | }
217 | });
218 | }
219 | else {
220 | return processTemplate(template, options, src, attrs);
221 | }
222 | }
223 |
224 | function processHtmlTag(options) {
225 | if (options.inline) {
226 | var content = options.files.map(grunt.file.read).join(options.EOL);
227 | return processHtmlTagTemplate(options, content);
228 | }
229 | else {
230 | var destDir = options.relative && isFileRegex.test(options.dest) ? path.dirname(options.dest) : options.dest;
231 |
232 | return options.files.map(function (f) {
233 | var url = (options.relative && !/^((http|https):)?(\\|\/\/)/.test(f)) ? path.relative(destDir, f) : f;
234 | url = url.replace(/\\/g, "/");
235 |
236 | if (options.prefix) {
237 | url = URL.resolve(options.prefix.replace(/\\/g, "/"), url);
238 | }
239 |
240 | if (options.suffix) {
241 | var suffix = typeof options.suffix === "function" ?
242 | options.suffix(f, url) :
243 | options.suffix;
244 |
245 | if (suffix) {
246 | url += "?" + suffix;
247 | }
248 | }
249 |
250 | return processHtmlTagTemplate(options, url);
251 | }).join(options.EOL);
252 | }
253 | }
254 |
255 | //#endregion
256 |
257 | //#region Processors / Validators / Templates
258 |
259 | var
260 | templates = {
261 | 'script': '',
262 | 'script-inline': '',
263 | 'style': ' href="<%= src %>" />',
264 | 'style-inline': ''
265 | },
266 | validators = {
267 | script: validateBlockWithName,
268 | style: validateBlockWithName,
269 | section: validateBlockWithName,
270 |
271 | process: validateBlockAlways,
272 | remove: validateBlockAlways,
273 |
274 | //base method
275 | validate: function (tag, params) {
276 | if (!validators[tag.type]) {
277 | return false;
278 | }
279 |
280 | return validators[tag.type](tag, params);
281 | }
282 | },
283 | processors = {
284 | script: processHtmlTag,
285 | style: processHtmlTag,
286 | section: function (options) {
287 | return options.files.map(function (f) {
288 | var content = grunt.file.read(f).toString();
289 |
290 | return options.recursive ?
291 | transformContent(content, options.params, options.dest) :
292 | content;
293 | }).join(options.EOL);
294 | },
295 |
296 | process: function (options) {
297 | return options.lines
298 | .map(function (l) { return processTemplate(l, options); })
299 | .join(options.EOL)
300 | .replace(new RegExp(regexTagStart), "")
301 | .replace(new RegExp(regexTagEnd), "");
302 | },
303 | remove: function (options) {
304 | if (!options.name) return "";
305 |
306 | var targets = options.name.split(",");
307 | if (targets.indexOf(grunt.task.current.target) < 0) {
308 | return options.lines.join(options.EOL).replace(new RegExp(regexTagStart), "").replace(new RegExp(regexTagEnd), "");
309 | }
310 |
311 | return "";
312 | },
313 |
314 | //base method
315 | transform: function (options) {
316 | return processors[options.type](options);
317 | }
318 | };
319 |
320 | //#endregion
321 |
322 | function ensureContent(content, params) {
323 | if (!params.EOL) {
324 | var match = content.match(/\r?\n/);
325 | params.EOL = match ? match[0] : "\n";
326 | }
327 |
328 | return content.replace(/\r?\n/g, params.EOL);
329 | }
330 |
331 | function transformContent(content, params, dest) {
332 | var tags = getBuildTags(content),
333 | config = grunt.config();
334 |
335 | tags.forEach(function (tag) {
336 | var raw = tag.lines.join(params.EOL),
337 | result = "", prefix = "", suffix = "",
338 | tagFiles = validators.validate(tag, params);
339 |
340 | if (tagFiles) {
341 | var options = _.extend({}, tag, {
342 | data: _.extend({}, config, params.data),
343 | files: tagFiles,
344 | dest: dest,
345 | prefix: params.prefix,
346 | suffix: params.suffix,
347 | relative: params.relative,
348 | EOL: params.EOL,
349 | params: params
350 | });
351 |
352 | result = processors.transform(options);
353 | }
354 | else if (tagFiles === false) {
355 | grunt.log.warn("Unknown tag detected: '" + tag.type + "'");
356 |
357 | if (!params.allowUnknownTags) {
358 | grunt.fail.warn("Use 'parseTag' or 'allowUnknownTags' options to avoid this issue");
359 | }
360 | }
361 | else if (tag.optional) {
362 | if (params.logOptionals) {
363 | grunt.log.warn("Tag with type: '" + tag.type + "' and name: '" + tag.name + "' is not configured in your Gruntfile.js but is set optional, deleting block !");
364 | }
365 | }
366 | else {
367 | grunt.fail.warn("Tag with type '" + tag.type + "' and name: '" + tag.name + "' is not configured in your Gruntfile.js !");
368 | }
369 |
370 | if (params.keepTags) {
371 | prefix = raw.match(new RegExp(regexTagStart + "\\s*"))[0];
372 | suffix = raw.match(new RegExp(regexTagEnd + "\\s*"))[0];
373 | }
374 |
375 | content = content.replace(raw, function () { return prefix + result + suffix; });
376 | });
377 |
378 | if (params.beautify) {
379 | content = beautify.html(content, _.isObject(params.beautify) ? params.beautify : {});
380 | }
381 |
382 | return content;
383 | }
384 |
385 | grunt.registerMultiTask('htmlbuild', "Grunt HTML Builder - Replace scripts and styles, Removes debug parts, append html partials, Template options", function () {
386 | var params = this.options({
387 | beautify: false,
388 | logOptionals: false,
389 | relative: true,
390 | basePath: false,
391 | keepTags: false,
392 | scripts: {},
393 | styles: {},
394 | sections: {},
395 | data: {},
396 | parseTag: 'build',
397 | processFiles: false,
398 | processPath: defaultProcessPath
399 | });
400 |
401 | setTagRegexes(params.parseTag);
402 |
403 | this.files.forEach(function (file) {
404 | var dest = file.dest || "",
405 | destPath, content;
406 |
407 | file.src.forEach(function (src) {
408 | if (params.processFiles) {
409 | params.filesContext = createFilesContext(src);
410 | }
411 |
412 | // replace files in the same folder
413 | if (params.replace) {
414 | destPath = src;
415 | }
416 | // copy original folder structure into dest folder and compile templates
417 | else if (params.basePath || params.basePath === "") {
418 | // new path = dest + (src path without basePath at the beginning)
419 | destPath = dest + src.substring(params.basePath.length, src.length);
420 | }
421 | // Regex for path
422 | else if (isFileRegex.test(dest)) {
423 | destPath = dest;
424 | }
425 | // default: copy all files into destination
426 | else {
427 | destPath = path.join(dest, path.basename(src));
428 | }
429 |
430 | content = grunt.file.read(src);
431 | content = ensureContent(content, params);
432 | content = transformContent(content, params, dest);
433 |
434 | // write the contents to destination
435 | grunt.file.write(destPath, content);
436 | grunt.log.ok("File " + destPath + " created !");
437 | });
438 | });
439 | });
440 | };
441 |
--------------------------------------------------------------------------------