gulp-block-head
Simple, fast tool to filter Single-File-Component style files into separate components.
This component was written out of frustration at the number of SFC supporting tools out there, none of which were simple, fast and unopinionated.
var blockHead = require('gulp-block-head');
var gulp = require('gulp');
gulp('./**/*.vue')
.pipe(blockHead({
// Wrap `<script component>` in Vue.component
'script#component': contents => `Vue.component(${contents});`,
// Accept regular script content with no attributes
script: contents => contents,
// Split CSS into its own file with a header
css: {
contents => `My CSS file\n${contents}`,
name: (path, block) => `${path}.css`, // Append '.css' to end of input file name (e.g. myComponent.vue -> myComponent.vue.css)
},
}))
Tradeoffs
Since this module is designed to be fast and unopinionated some tradeoffs with processing have to be made:
- File input is treated as lines, opening or closing block definitions are not supported
- The remainder of the matching opening or closing block is discarded - block points must be on lines by themselves
- By default any block items need to the very first things on their lines - putting a closing block mid-line will be ignored
The idea here is that we don't waste time throwing source code though gigantic XML processors to detect opening and closing sections. We just parse the files and splice the bits we're interested in.
Debugging
This module uses the debug module. Simply set DEBUG=block-head
(or any other compatible inclusion expression) to show output.
DEBUG=block-head gulp build
Examples
Given a .vue
file that looks like this:
<service singleton option="value">
module.exports = { ... };
</service>
<component>
module.exports = {
methods: {
doSomething() {
// ...
}
},
};
</component>
<template>
<div class="card">
<div class="card-header">
<h2>Test unit</h2>
</div>
<div class="card-body">
Hello World
</div>
</div>
</div>
You can split the above .vue file using a config such as:
gulp.task('build.vue', ['load:app'], ()=>
gulp.src([
'**/*.vue',
'!dist/**/*', // Don't get stuck in a loop
'!node_modules/**/*',
])
.pipe(sourcemaps.init())
.pipe(blockHead({
blocks: {
component: { // Accept something that should be registered via Vue.component()
sort: 9, // Add after templates
name: path => `components/${fspath.basename(path, '.vue')}.js`,
transform: (content, path) =>
`Vue.component('${fspath.basename(path, '.vue')}', `
+ _.trimEnd(content, ';')
.replace('{', `{\n\ttemplate: Vue.template('${fspath.basename(path, '.vue')}'),`)
+ ')'
},
service: { // Accept some code to store as a shared service
sort: 1,
name: path => `services/${fspath.basename(path, '.vue')}.js`,
transform: (content, path, block) => // Pass all attrs as arguments to the Vue.service() handler
_.isEmpty(block.attr)
? `Vue.service('${fspath.basename(path, '.vue')}', ${_.trimEnd(content, ';')});`
: `Vue.service('${fspath.basename(path, '.vue')}', ${_.trimEnd(content, ';')}, ${JSON.stringify(block.attr)});`
},
script: { // Just dump the code inline
sort: 0,
name: path => `scripts/${fspath.basename(path, '.vue')}.js`,
transform: content => content,
},
template: { // Wrap the HTML in a call to `Vue.template(id, html)`
sort: 5,
name: path => `templates/${fspath.basename(path, '.vue')}.js`,
transform: (content, path) => `Vue.template('${fspath.basename(path, '.vue')}', \`${_.trimEnd(content, ';').replace(/\`/g, '\\`')}\`);`,
},
},
}))
.pipe(replace(/\s*module.exports =\s*/, '')) // Remove syntactic sugar
.pipe(babel({
presets: ['@babel/env'],
}))
.pipe(concat('app.js'))
.pipe(uglify())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('./dist'))
Notes:
- In the above we assume that
Vue.template(id, [html])
exists and provides a simple caching template store. You could alternatively dump your HTML into seperate files and deal with it that way - Likewise
Vue.service(id, [object], [settings])
provides a caching service for objects, with additional options. Options are provided within a tag as attributes
API
This plugin exports a single Gulp / Vinyl compatible stream transformer accepting a settings object.
Each block definition accepts the following properties:
Key | Type | Default | Description |
---|---|---|---|
blocks |
Array or Object
|
{} |
Definition of the blocks |
blocks.[].id |
String |
(derived from object key) | The ID of the block |
blocks.[].filter |
function or boolean
|
false |
Whether to accept the block, if this is a function its called as (path, block)
|
blocks.[].name |
Function |
(path, block) => `${path}#${block.id} |
How to name the output file |
blocks.[].transform |
Function |
(contents, path, block) => contents |
How to transform the contents of the output file |
blocks.[].matchStart |
RegExp |
^<${block.id}(\s*.+?\s*)?>$ |
The matching start of the block |
blocks.[].matchEnd |
RegExp |
/^<\/${block.id}>$/ |
The matching end of the block |
blocks.[].sort |
Function or any |
0 |
Where to output this block, if this is a function its called as (path, block)
|
default |
Boolean or Object
|
false |
Whether to handle files when no blocks are found |
default.name |
Function |
Optional renamer for default resources | |
default.transform |
Function |
Optional transformer for default resources | |
default.include |
Function |
(path) => true |
Determine whether to use the file in the output. If false no more processing is done. Applies only to the default block |
backpressure |
Boolean , Number or "warn"
|
"warn" |
How to handle backpressure when buffering into a Vinyl stream. false = Ignore and continue silently, true - raise an error and exit, Number - wait this number of milliseconds and try again, "warn" - Print a warning but continue |
lineFeed |
String |
os.EOL |
Line feed character for this OS |
Notes:
- The
blocks
definitions can be in the form of an object or an array - The
default
ID is used when no matches are found within the file - A decision whether the file output should be included at all can be set with the
include
function within the default block - The
path
parameter withinname
andtransform
functions is the original path of the file, not the rewritten one - Tag attributes are available as
block.attr
. Single attributes default to true:<block attr1 attr2="value2">
becomes{attr1: true, attr2: 'value2'}
BlockHead.import(files, options)
In addition to a Gulp adpater this module also provides the shorthand import()
method which allows inline inclusion of JS files split into blocks.
This function will attempt to read then given files (which can be a single path, array or any valid gulp.src()
glob), extract the given blocks and run them with a context. This is designed to be as close as possible to the import
syntax.
var blockHead = require('gulp-block-head');
// Read the given HTML file and run all scripts specified in the 'script' blocks - this will execute in the current context
blockHead.import('./test/data/script.html', 'script')
.then(()=> ...)
// Read the given HTML file and run all scripts specified in the 'script', 'include' and 'import' blocks - passing in a custom sandbox context
blockHead.import('./test/data/script.html', {
blocks: ['script', 'include', 'import'],
sandbox: {foo: 'Foo!'},
})
.then(()=> ...)
This function supports the following options:
Key | Type | Default | Description |
---|---|---|---|
blocks |
String , Array or Object
|
"backend" |
The blocks to process, see the notes below for more details |
src |
Object |
{} |
Options to pass to gulp.src() when processing the file input |
sandbox |
Boolean or Object
|
false |
The sandbox to run the code in, if boolean false the current context is used |
Notes:
- If
blocks
is a single string or array of strings the transform function is constructed automatically, if its an object the transform is ignored and a full BlockHead spec must be specified instead - If
sandbox === false
the script will have access to all objects in the current scope, including any declared globals - Passing a single string or array to the
options
parameter will assume setting of theblocks
property