Automated Chrome extension Workflow with gulp
*this is not updated to current manifest v3
This time i revisit my chrome extension because i'm gonna create a new one. It still a work in progress, and i might update this post as i progress since there's a couple more gulp trick i like to try out. But the main focus here i wanted to automate my workflow from writing the source code in a single project folder to zipping it into an extension and ultimately upload it using gulp as well.I only got to the zipping, but haven't had the confident yet to automate the publishing part yet since the extension is still work in progress. The cons i find here is, there's a lot of package. But still using npm or yar we can get those in one single command. You can find all the package readme on npm or github.
Also worth to mention that i'm using gulp 4 and i din't use yeoman, so i create all the file and folder in the src
folder manually including the manifest
, and jsconfig
. Everything inside build
folder are create by gulp.
Btw, if you have question, need some help or feedback, don't hesitate to reach me on twitter.
The list of package:
- gulp
- autoprefixer
- cssnano
- gulp-htmlmin
- gulp-postcss
- gulp-rename
- gulp-replace
- gulp-sass
- gulp-sourcemaps
- gulp-terser-js
- gulp-zip
- yargs - this allow us to use arguments like
gulp --production
Updates
25 September 2020 - Added new task to clean up folder 25 July 2021 - Added Conclusion and future plan
Folder Structure
.
└── project_folder/
├── build/
│ ├── debug/
│ │ ├── img
│ │ ├── html/
│ │ │ ├── index.html
│ │ │ ├── css/
│ │ │ │ └── style.css
│ │ │ └── js/
│ │ │ ├── popup.js
│ │ │ ├── content.js
│ │ │ └── background.js
│ │ └── my_extension/
│ │ └── manifest.json
│ └── release/
│ └── my_extension.zip
├── src/
│ ├── sass
│ ├── html/
│ │ └── popup.html
│ └── js/
│ ├── popup.js
│ ├── content.js
│ └── background.js
├── gulpfile.js
├── jsconfig.json
├── package.json
├── package-lock.json
└── .gitignore
Steps
-
mkdir <PROJECT_FOLDER>
-
cd <PROJECT_FOLDER>
-
npm init
-
npm i -D - gulp autoprefixer gulp cssnano gulp-htmlmin gulp-postcss gulp-rename gulp-replace gulp-sass gulp-sourcemaps gulp-terser-js gulp-zip
-
Write the gulp file
The gulpfile
const { src, dest, watch, series, parallel } = require('gulp');
const terser = require('gulp-terser-js');
const htmlmin = require('gulp-htmlmin');
const zip = require('gulp-zip');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const postcss = require('gulp-postcss');
const sass = require('gulp-sass');
const sourcemaps = require('gulp-sourcemaps');
const fs = require('fs');
const argv = require('yargs').argv;
const manifestJson = json = JSON.parse(fs.readFileSync('./src/manifest.json'));
const appName = 'my_extension';
const appVersion = `${manifestJson.version}`;
const files = {
jsPath: 'src/**/*.js',
htmlPath: 'src/**/*.html',
scssPath: 'src/scss/**/*.scss',
imgPath: 'src/img/*.*'
}
const outputPath = {
mainPath: 'build/debug',
cssPath: 'build/debug/css',
jsPath: 'build/debug',
imgPath: 'build/debug/img',
}
function scssTask() {
return src(files.scssPath)
.pipe(sourcemaps.init())
.pipe(sass())
// .pipe(postcss([autoprefixer(), cssnano()]))
.pipe(postcss([autoprefixer()]))
.pipe(sourcemaps.write('.'))
.pipe(dest(outputPath.cssPath));
};
function htmlTask() {
return src(files.htmlPath)
.pipe(htmlmin({ collapseWhitespace: true, removeComments: true, minifyCSS: true }))
.pipe(dest(`${outputPath.mainPath}`));
};
function jsTask() {
return src(files.jsPath)
// .pipe(terser()) // uncomment when ready to minify
.pipe(dest(`${outputPath.mainPath}`));
};
function manifestTask() {
return src('src/*.*')
.pipe(dest(`${outputPath.mainPath}`));
}
function imgTask() {
return src(files.imgPath)
.pipe(dest(`${outputPath.imgPath}`));
}
function zipTask() {
return src(`${outputPath.mainPath}/**/*`)
.pipe(zip(`${appName}_${appVersion}.zip`))
.pipe(dest(`build/release`));
}
function uploadTask() {
// TODO
// most of the time we don't want to do it here
// we upload via a CICD after run test and merge it to master branch
}
function cleanFolder(cb) {
// syncronous operation cannot be include inside gulp task
// so we use asyncronouse with callback instead
fs.rmdir('build/debug', { recursive: true }, cb,);
}
function watchTask() {
watch([files.scssPath, files.jsPath, files.htmlPath],
parallel(scssTask, jsTask, htmlTask, manifestTask));
}
if (!(argv.production === undefined)) {
// this only run whem we use `gulp --production`
console.info('PRODUCTION_MODE');
// exports.default = series(parallel(scssTask, jsTask, htmlTask, imgTask), zipTask);
exports.default = series(cleanFolder, parallel(scssProdTask, jsTask, htmlTask, imgTask, manifestTask), zipTask);
} else {
// without argument
console.info('DEBUG_MODE');
exports.default = series(parallel(scssTask, jsTask, htmlTask, imgTask, manifestTask), watchTask);
}
Conclusion
This method is pretty hacky when i want to quickly get it up and running and depends on the pre ES6 javascript which run in the browser where a html file will have multiple script source arranged by their precedence. ES6 wouldn't work with this workflow. Stay tuned for updated version with ES6 support using rollup, Vite for hot module reloading, and maybe i'll add some svelte for more spice.