Transition from gulp to npm - npm

I am trying to move away from gulp and use npm scripts. We have developers that use different versions of node/gulp and the dependence on plugins and deprecation's has been an unwanted hassle.
I am trying to convert our gulp script to npm but there are a few areas I'm stuck on. I'm having issues converting from gulp-sourcemaps to npm map-stream and converting from gulp-uglify to uglifyjs.
Here is the gulp file we are currently using:
/*
This file in the main entry point for defining Gulp tasks and using Gulp plugins.
Click here to learn more. http://go.microsoft.com/fwlink/?LinkId=518007
*/
var gulp = require('gulp');
var uglify = require('gulp-uglify');
var rename = require('gulp-rename');
var sourcemaps = require('gulp-sourcemaps');
var pump = require('pump');
var del = require('del');
// set a variable telling us if we're building in release
var isRelease = true;
if (process.env.NODE_ENV && process.env.NODE_ENV !== 'Release') {
isRelease = false;
}
var config = {
//Include all js files but exclude any min.js files
src: ['Scripts/*.js', '!Scripts/*.min.js']
}
//delete the output file(s)
gulp.task('clean', function () {
//del is an async function and not a gulp plugin (just standard nodejs)
//It returns a promise, so make sure you return that from this task function
// so gulp knows when the delete is complete
return del(['Scripts/*.min.js', 'Scripts/Maps/*.map']);
});
// Combine and minify all files from the app folder
// This tasks depends on the clean task which means gulp will ensure that the
// Clean task is completed before running the scripts task.
gulp.task('scripts', ['clean'], function (cb) {
pump([
gulp.src(config.src),
sourcemaps.init(),
uglify({ mangle: isRelease }),
rename({ suffix: '.min' }),
sourcemaps.write('Maps', { includeContent: false, sourceRoot: './' }),
gulp.dest('Scripts/')
],
cb
);
});
//Set a default tasks
gulp.task('default', ['scripts'], function () { });
Here is my current npm script (still has some errors):
/*
This is the main entry point for defiinng npm tasks
*/
const del = require('del');
var map = require('map-stream');
var pump = require('pump');
var vfs = require('vinyl-fs');
var uglifyjs = require('uglify-js');
// set a variable telling us if we're building in release
var isRelease = true;
if (process.env.NODE_ENV && process.env.NODE_ENV !== 'Release') {
isRelease = false;
}
console.log(process.env.NODE_ENV);
//task to delete output files
(async () => {
const deletedPaths = await del(['Scripts/*.min.js', 'Scripts/Maps/*.map']);
console.log('Deleted files and folders:\n', deletedPaths.join('\n'));
})();
var log = function(file, cb) {
console.log(file.path);
cb(null, file);
};
// vinyl metadata object
// Include all js files but exclude any min.js files
pump([
vfs.src(['Scripts/*.js', '!Scripts/*.min.js']),
map(log),
uglifyjs({mangle:isRelease}),
rename({ suffix: '.min' }),
(vfs.dest('Scripts/'))
])
The mapping should create a Maps folder under the Scripts directory.
Scripts
--Maps
--jquery.min.js.map
--jquery-1.4.1.min.js.map
Any help would be appreciated.
Thanks!

/*
* This is the main entry point for defining npm tasks
* Author: *****
* Date: 06/03/2019
*/
const del = require('del');
var Uglify = require("uglify-js"),
fs = require('fs'),
async = require('async'),
path = require('path'),
rename = require('rename'),
parentDir = 'Scripts';
Checking the build type (Release, Production, Debug etc.) is done using node so there weren't any changes to be made in this part of the script.
// set a variable telling us if we're building in release
var isRelease = true;
if (process.env.NODE_ENV && process.env.NODE_ENV !== 'release') {
isRelease = false;
}
console.log(process.env.NODE_ENV);
As for the npm del module, I opted for the synchronous method. The reason is that I wanted each file to be processed separately instead of in parallel. The async method kept missing the .acingore file and there aren't many files that need to be deleted anyway.
// Returns an array of deleted paths
const deletedPaths = del.sync(['Scripts/*.min.js', 'Scripts/Maps/*.map', 'Scripts/.acignore']);
console.log('Deleted files and folders:\n', deletedPaths.join('\n'));
This block does all the work. This function runs an array of functions in series, each passing their results to the next in the array. Each function is passed a callback on completion.
async.waterfall([
function (cb) {
fs.readdir(parentDir, cb);
},
function (files, cb) {
// files is just an array of file names, not full path
console.log('Files being looped through:\n');
// run 10 files in parallel
async.eachLimit(files, 10, function (filename, done) {
var filePath = path.join(parentDir, filename);
var sourceFile = filename;
if (!fs.lstatSync(filePath).isDirectory()) {
// create a .temp file to be minified
fs.copyFileSync(filePath, filePath + '.temp', (err) => {
if (err) throw err;
// console.log(filePath + ' was copied to ' + filePath + '.temp');
});
// path the .temp file
var tempfilePath = path.join(parentDir, filename + '.temp');
// console.log('tempfilePath: ' + tempfilePath);
// check for /Maps directory, if not, create it
var mapsDir = parentDir + '/Maps';
try {
if (!fs.existsSync(mapsDir)) {
fs.mkdirSync(mapsDir)
}
} catch (err) {
console.error(err)
}
// rename file to add .min suffix for minified files
filename = rename(filename, { suffix: '.min' });
// console.log('filename after rename\n' + filename + '\n');
var newfilePath = path.join(parentDir, filename);
// console.log('filePath after rename\n' + newfilePath + '\n');
// Synchronous rename
fs.renameSync(tempfilePath, newfilePath);
// get contents of sourceFile
// The source file must be interpolated with [] in order
// to be mapped correctly, otherwise your map will get the content
// of the source file but not have a link back to the source
try {
var code = {
[sourceFile]: fs.readFileSync(filePath, 'utf-8')
};
console.log(code);
} catch (e) {
console.log('Error:', e.stack);
}
// minify file
// the syntax for uglifyjs minify is minify(code, options)
// therefore you need the contents of the file in order to minify it
// and source map it
var uglyCode = Uglify.minify(code,
{
mangle: isRelease,
sourceMap: {
includeSources: false,
filename: '../' + filename,
root: '../',
url: 'Maps/' + filename,
},
}
);
// console.log('Source file: ' + sourceFile);
// write minified file to directory
fs.writeFile(newfilePath, uglyCode.code, function (err) {
if (err) {
console.log(err);
} else {
console.log(filename + " has been mangled");
}
}
);
// write map file to directory
fs.writeFile(mapsDir + '/' + filename + '.map', uglyCode.map, function (err) {
if (err) {
console.log(err);
} else {
console.log(filename + '.map' + " has been mapped");
}
}
);
done();
}
}, cb);
}
], function (err) {
err && console.trace(err);
console.log('\nNo more files in path\n');
});
SIDE NOTE: If you're running node 10+ and you cannot set the path to npm in your .csproj file, install node globally and you don't need to set the path.
Hopefully my comments in the code are sufficient. Good luck if you are trying to move away from gulp and transition to npm!

Related

Nuxt 3 file upload and store in locally in the project

I want to create a simple Nuxt 3 file upload implementation that stores the file in the locally in a folder in the Nuxt project. In PHP the server side code is very easy and straight forward but I am finding it difficult doing the same thing in Nuxt 3 server side.
First:
npm install formidable
second:
define formidable in Nuxt config file inside modules list.
export default defineNuxtConfig({
modules: ["formidable"],
});
then in your handler for example upload.post.js :
import formidable from "formidable";
import fs from "fs";
import path from "path";
export default defineEventHandler(async (event) => {
let imageUrl = "";
let oldPath = "";
let newPath = "";
const form = formidable({ multiples: true });
const data = await new Promise((resolve, reject) => {
form.parse(event.req, (err, fields, files) => {
if (err) {
reject(err);
}
if (!files.photo) {
resolve({
status: "error",
message: "Please upload a photo with name photo in the form",
});
}
if (files.photo.mimetype.startsWith("image/")) {
let imageName =
Date.now() +
Math.round(Math.random() * 100000) +
files.photo.originalFilename;
oldPath = files.photo.filepath;
newPath = `${path.join("public", "uploads", imageName)}`;
imageUrl = "./public/upload/" + imageName;
fs.copyFileSync(oldPath, newPath);
resolve({
status: "ok",
url: imageUrl,
});
} else {
resolve({
status: "error",
message: "Please upload nothing but images.",
});
}
});
});
return data;
});
don't forget to name the input field "photo" in the client side or change it here in every "files.photo".
ALso the path of uploaded photos will be in public/uploads directory you can change it too if you like in "path.join" method.
Good luck

how to use rollup to parse cucumber feature files and backing step definition files

I have the following rollup plugin that will include .feature files
export const cucumberRollupPlugin: PluginImpl<CucumberOptions> = pluginOptions => {
let options: CucumberOptions = {
...{
include: '**/*.feature',
cwd: process.cwd(),
},
...pluginOptions,
};
let filter = createFilter(options);
let plugin: Plugin = {
name: 'bigtest-cucumber',
async transform(code, id) {
if (!filter(id)) {
return;
}
let parser = new GherkinParser({ code, uri: id, rootDir: options.cwd });
let result = await parser.parse();
let esm = dataToEsm(result, { namedExports: false });
// TODO: add sourcemap support
let transformResult: TransformResult = { code: esm };
return transformResult;
},
};
return plugin;
};
The problem I have is that to make the feature files work, there are step definition files that actually contain the functionality. So a feature file might look like this
Feature: Visit career guide page in career.guru99.com
Scenario: Visit career.guru99.com
Given: I browse to career.guru99.com
And a step definition file might look like this:
import { Given, When, Then from 'cucumber';
import assert from 'assert';
import{ driver } from '../support/web_driver';
Given(/^browse to web site "([^"]*)"$/, async function(url) {
return driver.get(url);
});
The problem I have with rollup is that there are no import statements for either the step definition files. The way cucumber-js works is that these files are found at runtime.
I think I need to generate an index.js that looks like this to cover the step definitions.
import from './step_definition_1';
import from './step_definition_2';
import from './step_definition_3';
Where would this fit in the rollup pipeline to generate this file so it can get pulled into the rollup pipeline.
You can use this.emitFile to manually process the .feature files and include them in the output. Call this.emitFile for each .feature file in the buildStart hook (each emitted file will get processed through the transform hook you wrote).
Here's an example that uses the globby package (which expands a glob to an array of file paths) to get the file path of each .feature file to pass to this.emitFile:
import globby from 'globby'
export const cucumberRollupPlugin: PluginImpl<CucumberOptions> = pluginOptions => {
let options: CucumberOptions = {
include: '**/*.feature',
cwd: process.cwd(),
...pluginOptions,
};
let filter = createFilter(options);
let plugin: Plugin = {
name: 'bigtest-cucumber',
async buildStart({ include, cwd }) {
const featureFilePaths = await globby(include, { cwd });
for (const featureFilePath of featureFilePaths) {
this.emitFile({
type: 'chunk',
id: featureFilePath
});
}
},
async transform(code, id) {
if (!filter(id)) {
return;
}
let parser = new GherkinParser({ code, uri: id, rootDir: options.cwd });
let result = await parser.parse();
let esm = dataToEsm(result, { namedExports: false });
// TODO: add sourcemap support
let transformResult: TransformResult = { code: esm };
return transformResult;
},
};
return plugin;
};
Let me know if you have any questions!
You need to use the this.emitFile method and befor that lookup the files via glob or anything else inside your plugin
{
plugins: [typescript(),{
name: "emit-additional-files",
async transform(code,id) {
//id === fileName
//code === fileContent
// inspect code or id here then use path.resolve() and fs.readdir to find additional files
this.emitFile()
}
}]
}

How to load webassembly file in Vue?

I have compiled the C code using this command emcc add.c -o js_plumbing.js -s -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -s MODULARIZE=1
This is my Vue component code -
public instance:any = {
ready: new Promise(resolve => {
Module({
onRuntimeInitialized() {
this.instance = Object.assign(this, {
ready: Promise.resolve()
});
resolve();
}
});
})
};
public draw_outline() {
this.instance.ready
.then(_ => this.result_web = this.instance.addTwoNumbers(2,2));
}
draw_outline is getting called when I click on a text element.
And this is the error I'm getting -
So after this error I went to generate file and just added export to the module and this error disappears. but now my function in C "addTwoNumbers" is not getting called from instance.
if I print the value of instance I get
Does anyone know how to proceed from here?
I figured that when compiling I needed to use USE_ES6_IMPORT_META=0 flag so that WebAssembly module will use an older version of the import.meta.url line of code for systems that don't recognize the import style. so the command looks like emcc add.c -o js_plumbing.js -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -s ENVIRONMENT='web,worker' -s EXPORT_ES6=1 -s MODULARIZE=1 -s USE_ES6_IMPORT_META=0
This is my updated code -
Module().then(myModule => {
const result = myModule.ccall('addTwoNumbers',
'number',
['number', 'number'],
[4, 6]);
console.log("Value from wasm file", result);
});
My config file -
const path = require('path');
const contentBase = path.resolve(__dirname, '..', '..');
module.exports = {
configureWebpack: config => {
config.devServer = {
before(app) {
// use proper mime-type for wasm files
app.get('*.wasm', function (req, res, next) {
var options = {
root: contentBase,
dotfiles: 'deny',
headers: {
'Content-Type': 'application/wasm'
}
};
res.sendFile(req.url, options, function (err) {
if (err) {
next(err);
}
});
});
}
}
},
}
It is inside a function that I call on a click event . I can elaborate the whole process if someone is interested. It should not take this much time for anyone, I hope it helps others who have been looking for the solution. I realise I have not properly stated the problem in this post, I shall update everything here in a proper way soon.

Native support for ES6 in PhantomJS

is there a way to make PhantomJS natively support ES6, I have a bunch of ES6 code which is converted to ES5 via Babel, what I need to accomplish is accurate measurement of code coverage which is done for ES6 code rather than ES5. It's a requirement from client, so I can't just tell him to stop requesting such thing...
Afaik NodeJS already has native support for ES6, is there a way to do that with PhantomJS?
I've ended up using raw NodeJS (without PhantomJs) + Express + JSDom (https://github.com/tmpvar/jsdom), the POC looks like this:
"use strict"
const $module = require('module');
const path = require('path');
const babel = require("babel-core");
const Jasmine = require('jasmine');
const reporters = require('jasmine-reporters');
const express = require('express');
const jsdom = require("jsdom");
const app = express();
const vm = require('vm');
const fs = require("fs");
app.get('/', function (req, res) {
res.sendFile('index.html', { root: __dirname });
});
app.use('/bower_components', express.static('bower_components'));
const load = function (filename) {
return fs.readFileSync(`./bower_components/${filename}`, "utf-8");
};
const packages = [
fs.readFileSync('./bower_components/jquery/dist/jquery.js', "utf-8"),
fs.readFileSync('./bower_components/angular/angular.js', "utf-8"),
fs.readFileSync('./bower_components/angular-mocks/angular-mocks.js', "utf-8")
];
const sut = {
'./js/code.js': fs.readFileSync('./js/code.js', "utf-8")
};
const tests = {
'./tests/test.js': fs.readFileSync('./tests/test.js', "utf-8")
};
function navigate(FakeFileSystem, root, cwd, filename) {
// Normalize path according to root
let relative = path.relative(root, path.resolve(root, cwd, filename));
let parts = relative.split(path.sep);
let iterator = FakeFileSystem;
for (let part of parts) {
iterator = iterator[part] || (iterator[part] = { });
}
return iterator;
}
const server = app.listen(3333, function () {
const host = server.address().address;
const port = server.address().port;
const url = `http://${host === '::' ? 'localhost' : host}:${port}`;
console.log(`Server launched at ${ url }`);
console.log(`Running tests...`)
jsdom.env({
url: url,
src: packages,
done: function (err, window) {
let jasmine = new Jasmine();
let FakeFileSystem = {};
let descriptors = [];
jasmine.configureDefaultReporter({ showColors: true });
let env = jasmine.env;
for (let propertyName in env) {
if (env.hasOwnProperty(propertyName)) {
window[propertyName] = env[propertyName];
}
}
let context = vm.createContext(window);
let collections = [sut, tests];
for (let collection of collections) {
for (let filename in collection) {
let descriptor = navigate(FakeFileSystem, __dirname, '.', filename);
let source = collection[filename];
let transpiled = babel.transform(source, { "plugins": ["transform-es2015-modules-commonjs"] });
let code = $module.wrap(transpiled.code);
let _exports = {};
let _module = { exports: _exports };
descriptor.code = vm.runInContext(code, context);
descriptor.module = _module;
descriptor.exports = _exports;
descriptor.filename = filename;
descriptors.push(descriptor);
}
}
for (let descriptor of descriptors) {
let cwd = path.dirname(path.relative(__dirname, descriptor.filename));
descriptor.code.call(
undefined,
descriptor.exports,
// Closure is used to capture cwd
(function (cwd) {
return function (filename) { // Fake require function
return navigate(FakeFileSystem, __dirname, cwd, filename).exports;
}
})(cwd),
descriptor.module,
descriptor.filename
);
}
jasmine.execute();
server.close();
}
});
});
The beauty of this approach is that there is no need in transpiling code with babel, it allows frontend packages such as Angular to get loaded from bower, while all the config stuff comes from npm...
EDIT
I've stumbled upon the fact that NodeJS doesn't support all ES6 features yet, and such feature as ES6 modules is a real pain, they aren't supported anywhere, so I've ended up with doing partial transpilation of code with babel, with the expectation that as NodeJS will start providing richer and richer support for ES6 I will eventually turn-off babel features step by step and switch to native support when it will become available...

Gulp error: events.js:72

I've been trying out (or trying to get working) a jekyll style guide from: https://github.com/davidhund/jekyll-styleguide#user-content-requirements
My gulpfile is:
var gulp = require('gulp');
var sass = require('gulp-ruby-sass');
var autoprefixer = require('gulp-autoprefixer');
var browserSync = require('browser-sync');
var rename = require('gulp-rename');
var concat = require('gulp-concat');
var minifycss = require('gulp-minify-css');
var uglify = require('gulp-uglify');
var clean = require('gulp-clean');
var notify = require('gulp-notify');
var plumber = require('gulp-plumber');
// Handy file paths
paths = {
scss: "./static/scss/",
css: "./static/css/",
img: "./static/img/",
js: "./static/js/"
}
// SASS
gulp.task('sass', function() {
// Be specific in what file to process
return gulp.src(paths.scss+'app.scss')
.pipe(sass({ style: 'expanded' }))
.pipe(autoprefixer('> 5%', 'last 2 version', 'ie 9'))
.pipe(minifycss())
.pipe(rename({suffix: '.min'}))
.pipe(gulp.dest(paths.css))
// .pipe(gulp.dest('./_site/static/css/'))
// .pipe(notify({ message: 'Styles task complete' }));
});
// COPY CSS
gulp.task('copycss', function() {
return gulp.src(paths.css+'app.min.css')
.pipe(gulp.dest('./_site/static/css/'))
// .pipe(notify({ message: 'Copied Minified CSS to _site/static/css' }));
});
// JEKYLL
// Start a `jekyll build` task
// From: http://stackoverflow.com/questions/21293999/use-jekyll-with-gulp
gulp.task('jekyll-build', function() {
require('child_process').spawn('jekyll', ['build', '--config=_config.dev.yml'], {stdio: 'inherit'});
});
// Start a `jekyll build --watch` task
gulp.task('jekyll-watch', function() {
require('child_process').spawn('jekyll', ['build', '--watch', '--config=_config.dev.yml'], {stdio: 'inherit'});
});
// BROWSER-SYNC
gulp.task('browser-sync', function() {
// reload when Jekyll-generated files change
browserSync.init(['./_site/static/**/*.css', './_site/**/*.html'], {
server: {
baseDir: './_site/'
}
});
});
// WATCH
gulp.task('watch', function() {
// TEST: [Only] Run `jekyll build` when I update (the version in) settings.yml
// gulp.watch('./_config.yml', ['jekyll']);
// Run Sass when I update SCSS files
gulp.watch(paths.scss+'**/*.scss', ['sass', 'copycss']);
// gulp.watch(paths.js+'**/*.js', ['scripts']);
// gulp.watch(paths.img+'**/*', ['images']);
});
// DEFAULT task
gulp.task('default', ['jekyll-watch', 'watch','browser-sync']);
Whenever I run gulp I just get:
events.js:72
throw er; // Unhandled 'error' event
^
Error: spawn ENOENT
at errnoException (child_process.js:998:11)
at Process.ChildProcess._handle.onexit (child_process.js:789:34)
The problem you are facing is that you are not handling error, so, when gulp finds an error, it throw it, but "nobody" is taking care of it, which causes gulp to break.
In order to keep executing gulp, you have to define your error handlers and do whatever you want to do with error, typically, print on the cli what is going on.
You also need to identify which part of your code is "throwing" the error, in your case, its caused by the "watchers": a watcher listen for additional events or to add files to the watch. So, a watcher is throwing the error.
You have to catch it !
Add an on handler event after the execution of plugins, and pass this error to a function that will pint it (or something else), but will not break "the watch" (John Snow will be proud) and allows you to identify the error, fix it, at keep watching without restarting gulp manually.
PS: Don't forgot to define "the catcher function" !
Your code could be something like this:
var gulp = require('gulp');
var sass = require('gulp-ruby-sass');
var autoprefixer = require('gulp-autoprefixer');
var browserSync = require('browser-sync');
var rename = require('gulp-rename');
var concat = require('gulp-concat');
var minifycss = require('gulp-minify-css');
var uglify = require('gulp-uglify');
var clean = require('gulp-clean');
var notify = require('gulp-notify');
var plumber = require('gulp-plumber');
// Handy file paths
paths = {
scss: "./static/scss/",
css: "./static/css/",
img: "./static/img/",
js: "./static/js/"
}
// SASS
gulp.task('sass', function() {
// Be specific in what file to process
return gulp.src(paths.scss+'app.scss')
.pipe(sass({ style: 'expanded' })).on('error', errorHandler)
.pipe(autoprefixer('> 5%', 'last 2 version', 'ie 9'))
.pipe(minifycss())
.pipe(rename({suffix: '.min'}))
.pipe(gulp.dest(paths.css))
// .pipe(gulp.dest('./_site/static/css/'))
// .pipe(notify({ message: 'Styles task complete' }));
});
// COPY CSS
gulp.task('copycss', function() {
return gulp.src(paths.css+'app.min.css')
.pipe(gulp.dest('./_site/static/css/'))
// .pipe(notify({ message: 'Copied Minified CSS to _site/static/css' }));
});
// JEKYLL
// Start a `jekyll build` task
// From: http://stackoverflow.com/questions/21293999/use-jekyll-with-gulp
gulp.task('jekyll-build', function() {
require('child_process').spawn('jekyll', ['build', '--config=_config.dev.yml'], {stdio: 'inherit'});
});
// Start a `jekyll build --watch` task
gulp.task('jekyll-watch', function() {
require('child_process').spawn('jekyll', ['build', '--watch', '--config=_config.dev.yml'], {stdio: 'inherit'});
});
// BROWSER-SYNC
gulp.task('browser-sync', function() {
// reload when Jekyll-generated files change
browserSync.init(['./_site/static/**/*.css', './_site/**/*.html'], {
server: {
baseDir: './_site/'
}
});
});
// WATCH
gulp.task('watch', function() {
// TEST: [Only] Run `jekyll build` when I update (the version in) settings.yml
// gulp.watch('./_config.yml', ['jekyll']);
// Run Sass when I update SCSS files
gulp.watch(paths.scss+'**/*.scss', ['sass', 'copycss']);
// gulp.watch(paths.js+'**/*.js', ['scripts']);
// gulp.watch(paths.img+'**/*', ['images']);
});
// DEFAULT task
gulp.task('default', ['jekyll-watch', 'watch','browser-sync']);
// Handle the error
function errorHandler (error) {
console.log(error.toString());
this.emit('end');
}
Note the error handler definition at the end and the addition of .on('error', errorHandler) on your sass task.