questions about gulp recipe for browserify - browserify

The gulp recipes on github specifically says to use stackexchange for questions instead of bug reports, that's why I am here.
I am looking at the Browserify + Globs recipe, and I have some questions.
Question: what app.js?
bundledStream
// turns the output bundle stream into a stream containing
// the normal attributes gulp plugins expect.
.pipe(source('app.js'))
// the rest of the gulp task, as you would normally write it.
// here we're copying from the Browserify + Uglify2 recipe.
.pipe(buffer())
The example specifically is about using globs to match multiple files. Why do they specify a single app.js file? Do I need to actually have some app.js somewhere, and if so, what should be in it?
Question: is there an alternate form for this?
globby(['./entries/*.js']).then(function(entries) {
// create the Browserify instance.
var b = browserify({
entries: entries,
debug: true,
transform: [reactify]
});
the whole reason I am looking at this recipe is because I was using gulp-browserify, but it is unmaintained and doesn't have browserify(...).transform(), and I need to transform with babelify with presets: es2016, es2015. And it turns out, you can't just do the obvious thing: transform: [babelify, {presets: ['es2015','es2016']}], because this doesn't work.
Likewise, you can't just rely on the .babelrc file and a plain transform: [babelify], because that doesn't work (I see imports are working, but destructured parameters like constructor({val_a=1, val_b}) do not).
Overall, I want to convert this working task:
return gulp.src(['dev/scripts/**/*.js', '!dev/scripts/vendor/**/*.js'])
.pipe(plumber({
handleError: function (err) {
console.log(err);
this.emit('end');
}
}))
.pipe(browserify())
.pipe(gulp.dest('build/scripts'))
.pipe(rename({
suffix: '.min'
}))
.pipe(uglify())
.pipe(gulp.dest('build/scripts'))
.pipe(reload({ stream: true }))
to one that uses regular browserify, and babelify with .transform("babelify", {presets: ["es2016"]})

So it turns out the form you need is babelify.configure({})
My task is working in this form:
gulp.task('scripts', function () {
let scripts = ['dev/scripts/**/*.js', '!dev/scripts/vendor/**/*.js']
let bundledStream = through()
bundledStream
.pipe(source('main.min.js'))
.pipe(buffer())
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(uglify())
.pipe(plumber({
handleError: function (err) {
console.log(err)
this.emit('end')
}
}))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('./build/scripts'))
.pipe(reload({ stream: true }))
globby(scripts).then(function(entries) {
let b = browserify({
entries: entries,
debug: true,
transform: [babelify.configure({
presets: ["es2016", "es2015"]
})]
})
b.bundle().pipe(bundledStream)
}).catch(function(err) {
bundledStream.emit('error', err)
})
return bundledStream
})

Related

How to migrate to gulp v4 from 3?

gulp.watch('watch', function() {
watch('./app/index.html', function() {
gulp.start('html');
});
});
I want to run a task named 'html' when any changes to the file are made. It worked in the previous version of gulp for as for now it generates the following error.
gulp.start is not a function.
I can't find any way to achieve this in the newer version of the gulp. All I found that I need to change it to function, but I can't seem to find what I need to change and how?
The rest of the code is as follows
var gulp = require("gulp"),
watch = require('gulp-watch');
gulp.task('default', function(done){
console.log("You created the default task");
done();``
});
gulp.task('html', function(done){
console.log('modifying the html');
done();
});
gulp.watch('watch', function() {
watch('./app/index.html', function() {
gulp.start('html');
});
});
You don't need to convert your tasks to named functions - although that is considered best practice and is easy to do.
To fix your watch task, try:
gulp.watch('watch', function(done) {
watch('./app/index.html', gulp.series('html'));
done();
});
To change to named functions:
function html(done) {
gulp.src(….)
console.log('modifying the html');
done();
};
function watch(done) {
watch('./app/index.html', gulp.series('html'));
done();
});
exports.html= gulp.series(html);
exports.default = gulp.series(watch);
Note that now the watch task is not called as a string, i.e., 'watch', but just watch.
In exports.html, the gulp.series is not strictly needed as there is only one task there so exports.html= html; is sufficient.
And you only need to export a task if you wish to call it directly (as from the command line gulp html for example). If say the html task will only be called internally by other tasks then there is no need to export it.

How to change content of VuePress page via Plugin

I am trying to set up a plugin to change the content of a VuePress markdown file on the dev server and production build. According to documentation, I should be able to use the _content and _strippedContent that is available to me with the extendPageData
The following code is what I have set up in a plugin to do this.
module.exports = (options = {}, context) => ({
extendPageData($page) {
const {
_filePath, // file's absolute path
_computed, // access the client global computed mixins at build time, e.g _computed.$localePath.
_content, // file's raw content string
_strippedContent, // file's content string without frontmatter
key, // page's unique hash key
frontmatter, // page's frontmatter object
regularPath, // current page's default link (follow the file hierarchy)
path, // current page's real link (use regularPath when permalink does not exist)
} = $page
$page._content = "replaced"
$page._strippedContent = "replaced"
}
})
The best I can tell is that this code should work as it updates the $page._content however it is not showing testing but rather the original content.
I know that I am getting into this code as I can console.log from the file and it shows in the console.
I fear that $page._content is immutable and wonder if there is a way to do this kind of content swapping during dev or build
The information in those page objects is used after the markdown has been compiled and during the Vue component rendering. The content is more there for reference and modifying it won't have the effect you are after.
This tripped me up as well.
All the markdown files are processed for inoformation, but then the actual compilation occurs through webpack. The general flow is:
.md -> markdown-loader -> vue-loader -> ...
My recommendation and what I have done is to create a custom webpack loader to modify the content before it goes through the VuePress markdown loader. I used this approach to run my markdown files through Nunjucks for templating pre-markdown compilation. It's increadibly easy to do this after you figure out the correct approach :) Here is a working approach:
config.js:
chainWebpack: config => {
config.module
.rule('md')
.test(/\.md$/)
.use(path.resolve(__dirname, './nunjucks'))
.loader(path.resolve(__dirname, './nunjucks'))
.end()
},
And then a simple loader can look like this(abridged):
module.exports = function(source) {
const rendered = nunjucks.renderString(source, config)
return rendered
}
I think that You should do this with use of extendMarkdown https://v1.vuepress.vuejs.org/config/#markdown-extendmarkdown.
Try like this
// index.js
module.exports = () => ({
name: 'vuepress-plugin-foo-bar',
extendMarkdown: md => {
const render = md.render;
md.render = (...args) => {
// original content
const html = render.call(md, ...args);
return 'new content';
};
},
});
You can to modify your .vuepress/config.js. For example, if you want to replace '---my text---' with 'MY TEXT' (with uppercase, for example) in all yours files markdown, you have to add the next code to .vuepress/config.js, into chainWebpack section where I use a regex expression:
// File: .vuepress/config.js
module.exports = {
...,
chainWebpack: config => {
// Each loader in the chain applies transformations to the processed resource:
config.module
.rule('md')
.test(/\.md$/)
.use("string-replace-loader")
.loader("string-replace-loader")
.options({
multiple: [{
search: '---(.*?)---',
replace: (match, p1, offset, string, groups) => `<div><p class="myclass">${p1.toUpperCase()}</p></div>`,
flags: 'ig'
},
{
search: ' ... ',
replace: (match, p1, p2, ..., pn, offset, string) => ` ... `,
flags: 'ig'
}
],
}, )
.end()
},
};

How to cache .mp4 files in Safari with workbox-webpack-plugin?

I'm having exactly the same issue reported at https://github.com/GoogleChrome/workbox/issues/1663 which describes an issue that occurs exclusively in Safari where mp4 videos are not rendered after being cached by the service worker.
I'm using workbox-webpack-plugin, so the instructions provided in the comment https://github.com/GoogleChrome/workbox/issues/1663#issuecomment-448755945 will not work in my case. I'm not being able to require workbox-range-requests plugin in my webpack config file and pass it to the runtime caching options because I believe this package is intended for browser usage only. My workbox config is precaching .mp4 assets and uses a network first strategy for runtime caching.
How can I setup workbox-range-requests with workbox-webpack-plugin?
EDIT: Following Jeff's answer below, I've adjusted my webpack config to the following:
new WorkboxPlugin.InjectManifest({
swSrc: serviceWorkerSrcPath,
swDest: serviceWorkerBuildPath,
importsDirectory: 'sw',
})
The build produces the following service worker:
importScripts("/_build/sw/precache-manifest.8a0be820b796b153c97ba206d9753bdb.js", "https://storage.googleapis.com/workbox-cdn/releases/3.6.2/workbox-sw.js");
workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
workbox.routing.registerRoute(
/.*\.mp4/,
new workbox.strategies.CacheFirst({
cacheName: 'videos',
plugins: [
new workbox.cacheableResponse.Plugin({ statuses: [200] }),
new workbox.rangeRequests.Plugin(),
],
}),
);
If forgot to mention previously, but I've also added crossOrigin="anonymous" attribute to the video elements.
EDIT:
Repro that demonstrates it does not work as expected on Safari: https://github.com/acostalima/workbox-range-requests-mp4-demo
There's specific guidance for this use case in the "Serve cached audio and video" recipe in the Workbox documentation.
You can continue using the workbox-webpack-plugin, but I'd suggest using it in InjectManifest mode, which will give you control over the top-level service worker file. That will in turn make it possible to follow the recipe.
This documentation has guidance on configuring workbox-webpack-plugin in InjectManifest mode.
I had the same issue with Safari and managed to resolve it by removing my video from the precahe list self.__precacheManifest and instead by adding it in the service worker's install handler:
self.addEventListener('install', (event) => {
const urls = [/* videoUrl */];
const cacheName = 'videos';
event.waitUntil(caches.open(cacheName).then((cache) => cache.addAll(urls)));
});
Looking at the logs, it seemed that otherwise only the precache was used to respond to the request for the video resource and not the router.
Although the docs say that adding mp4s to the precache cache and then configuring the range plugin to handle precache mp4s is supposed to work, in practice, it wasn't. Removing mp4s from the precache and configuring your own video cache with the range plugin did the trick for me. Don't forget to add the crossorigin="anonymous" tag to your videos!
Here's how I did it (webpack 5, workbox 6):
// src/service-worker.js
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
import { cacheNames } from 'workbox-core';
import { precacheAndRoute } from 'workbox-precaching';
import { RangeRequestsPlugin } from 'workbox-range-requests';
import { registerRoute } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';
const allEntries = self.__WB_MANIFEST; // Injected by WorkboxWebpackPlugin at compile time
const videoEntries = allEntries.filter((entry) => entry.url.endsWith('.mp4'));
const restEntries = allEntries.filter((entry) => !entry.url.endsWith('.mp4'));
precacheAndRoute(restEntries);
const videoCacheName = `${cacheNames.prefix}-videos-${cacheNames.suffix}`;
self.addEventListener('install', (event) => {
const allVideosAddedToCache = caches.open(videoCacheName).then((videoCache) => {
const videoUrls = videoEntries.map((entry) => entry.url);
return videoCache.addAll(videoUrls);
});
event.waitUntil(allVideosAddedToCache);
});
registerRoute(
(route) => route.url.pathname.endsWith('.mp4'),
new CacheFirst({
cacheName: videoCacheName,
plugins: [new CacheableResponsePlugin({ statuses: [200] }), new RangeRequestsPlugin()],
})
);
// webpack.config.js
plugins: [
new WorkboxWebpackPlugin.InjectManifest({
swSrc: 'src/service-worker.js',
}),
]
// index.tsx
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js');
});
}

Exit with error after a task loaded by gulp-tasks failed

I am working with magento2-frontools and try to solve this issue:
https://github.com/SnowdogApps/magento2-frontools/issues/231
The problem is, that gulp styles should have a non-zero exit code in case of errors, but it exits with 0.
The gulp file looks like this:
// Tasks loading
require('gulp-task-loader')({
dir : 'task',
plugins: plugins,
configs: config
});
And the styles.js task like this:
'use strict';
module.exports = function() { // eslint-disable-line func-names
// Global variables
const gulp = this.gulp,
plugins = this.opts.plugins,
config = this.opts.configs,
themes = plugins.getThemes(),
streams = plugins.mergeStream();
// Generate all necessary symlinks before styles compilation, but ony if not a part of tasks pipeline
if (!plugins.util.env.pipeline) {
plugins.runSequence('inheritance');
}
// Loop through themes to compile scss or less depending on your config.json
themes.forEach(name => {
streams.add(require('../helper/scss')(gulp, plugins, config, name));
});
return streams;
};
(it can all be found on GitHub)
If have seen this approach to solve the problem:
.once("error", function () {
this.once("finish", () => process.exit(1));
})
But where can I add that code?
Just the --ci flag has to be used.

Find by data-dojo-id on Intern-runner functional test?

Is there a way to grab a reference to a widget instance by data-dojo-id on an Intern functional test running on a standalone server?
Yes, Dojo released a dijit-intern-helper module that you can include in your tests to help with this:
define([
'intern!object',
'intern/chai!assert',
'intern/dojo/node!dijit-intern-helper/helpers/dijit',
'require'
], function (registerSuite, assert, dijit, require) {
var url = '../../index.html';
registerSuite({
name: 'Todo (functional)',
'get widget node': function () {
return this.remote
.get(require.toUrl(url))
.then(dijit.nodeById('yourWidgetId', 'rootNodeToLookUnder'))
.getProperty('value')
.then(function (val) {
assert.ok(val == 'Test :)');
});
}
});
});
You can read more about it on this Sitepen blog post or straight on the project Github page.