我把熬夜录制的讲解视频也放在下面了,本宝宝是不是很贴心
脚手架的实现过程就是在启动脚手架之后,自动地去询问一些预设问题,通过回答的结果结合一些模板文件,生成项目的结构。
使用NodeJS开发一个小型的脚手架工具:
用yarn init
初始化一个空文件夹:jal-pro
在package.json
中添加bin
属性指定脚手架的命令入口文件为cli.js
{
"name": "jal-pro",
"version": "1.0.0",
"main": "index.js",
"bin": "cli.js",
"license": "MIT",
"dependencies": {
"ejs": "^3.1.3",
"inquirer": "^7.1.0"
}
}
编写cli.js
#!/usr/bin/env node
// Node CLI 应用入口文件必须要有这样的文件头
// 如果Linux 或者 Mac 系统下,还需要修改此文件权限为755: chmod 755 cli.js
// 脚手架工作过程:
// 1. 通过命令行交互询问用户问题
// 2. 根据用户回答的结果生成文件
const path = require('path')
const fs = require('fs')
const inquirer = require('inquirer') // 发起命令行交互询问
const ejs = require('ejs') // 模板引擎
inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Project name?'
}
]).then(answer => {
console.log(answer)
// 模板目录
const tempDir = path.join(__dirname, 'templates')
// 目标目录
const destDir = process.cwd()
// 将模板下的文件全部转换到目标目录
fs.readdir(tempDir, (err, files) => {
if (err) throw err
files.forEach(file => {
// 通过模板引擎渲染文件
ejs.renderFile(path.join(tempDir, file), answer, (err, result) => {
if(err) throw err
// 将结果写入到目标目录
fs.writeFileSync(path.join(destDir, file), result)
})
})
})
})
命令行中修改cli.js
文件权限:chmod 755 cli.js
模板文件templates/index.html
如下:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= name %>title>
head>
<body>
body>
html>
执行命令将该cli程序link到全局:yarn link
然后再其他文件夹中执行:jal-pro
命令,就可以根据模板自动化创建文件了
gulp讲解
// 实现这个项目的构建任务
const {src, dest, parallel, series, watch} = require('gulp')
const del = require('del')
const browserSync = require('browser-sync')
const bs = browserSync.create()
const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()
const {sass, babel, swig, imagemin, ghPages, eslint, sassLint} = plugins
const config = {
production: false,
port: 2080,
open: false
}
const isMini = () => config.production
const calculateConfig = () => {
const argv = process.argv
console.log(argv)
const task = argv[2]
if(task === 'serve') {
config.production = false
config.open = argv.includes('--open')
config.port = argv.includes('--port') && parseInt(argv[argv.indexOf('--port')+1], 10) || 2080
config.root = 'temp'
} else if (task === 'build') {
config.production = argv.includes('--production') || argv.includes('--prod')
} else if (task === 'start') {
config.open = argv.includes('--open')
config.port = argv.includes('--port') && parseInt(argv[argv.indexOf('--port')+1], 10) || 2080
config.root = 'dist'
} else if (task === 'deploy') {
config.production = true
config.branch = argv.includes('--branch') && argv[argv.indexOf('--branch')+1] || 'gh-pages'
}
console.log('config', config)
}
calculateConfig()
const data = {
menus: [
{
name: 'Home',
icon: 'aperture',
link: 'index.html'
},
{
name: 'Features',
link: 'features.html'
},
{
name: 'About',
link: 'about.html'
},
{
name: 'Contact',
link: '#',
children: [
{
name: 'Twitter',
link: 'https://twitter.com/w_zce'
},
{
name: 'About',
link: 'https://weibo.com/zceme'
},
{
name: 'divider'
},
{
name: 'About',
link: 'https://github.com/zce'
}
]
}
],
pkg: require('./package.json'),
date: new Date()
}
// Clean the dist & temp files.
const clean = () => {
return del(['dist', 'temp'])
}
const myeslint = () => {
return src(['src/assets/scripts/*.js'])
.pipe(eslint({
rules: {
'my-custom-rule': 1,
'strict': 2
},
globals: [
'jQuery',
'$'
],
envs: [
'browser'
]
}))
.pipe(eslint.format())
}
const mysasslint = () => {
return src(['src/assets/styles/*.scss'])
.pipe(sassLint())
.pipe(sassLint.format())
.pipe(sassLint.failOnError())
}
const style = () => {
return src('src/assets/styles/*.scss', { base: 'src' })
.pipe(sass({ outputStyle: 'expanded' }))
.pipe(dest('temp'))
.pipe(bs.reload({stream: true}))
}
const script = () => {
return src('src/assets/scripts/*.js', { base: 'src' })
.pipe(babel({ presets: ['@babel/preset-env'] }))
.pipe(dest('temp'))
.pipe(bs.reload({stream: true}))
}
const page = () => {
return src('src/**/*.html', { base: 'src' })
.pipe(swig({ data, defaults: { cache: false } })) // 防止模板缓存导致页面不能及时更新
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
const image = () => {
return src('src/assets/images/**', {base: 'src'})
.pipe(imagemin())
.pipe(dest('dist'))
}
const font = () => {
return src('src/assets/fonts/**', {base: 'src'})
.pipe(imagemin())
.pipe(dest('dist'))
}
const extra = () => {
return src('public/**', {base: 'public'})
.pipe(dest('dist'))
}
const browser = () => {
watch('src/assets/styles/*.scss', style)
watch('src/assets/scripts/*.js', script)
watch('src/*.html', page)
watch([
'src/assets/images/**',
'src/assets/fonts/**',
'public/**'
], bs.reload)
bs.init({
notify: false,
port: config.port,
open: config.open,
// files: 'temp/**',
server: {
baseDir: [config.root, 'src', 'public'], // 按顺序查找
routes: {
'/node_modules': 'node_modules'
}
}
})
}
const useref = () => {
return src('temp/*.html', { base: 'temp' })
.pipe(plugins.useref({ searchPath: ['temp', '.'] }))
.pipe(plugins.if(/\.js$/, plugins.uglify()))
.pipe(plugins.if(/\.css$/, plugins.cleanCss()))
.pipe(plugins.if(/\.html$/, plugins.htmlmin({
collapseWhitespace: isMini(),
minifyCSS: isMini(),
minifyJS: isMini()
})))
.pipe(dest('dist'))
}
const mydeploy = () => {
return src('dist/**/*')
.pipe(ghPages([{
branch: config.branch
}]))
}
const lint = parallel(myeslint, mysasslint)
const compile = parallel(style, script, page)
const serve = series(compile, browser)
const build = series(
clean,
parallel(
series(compile, useref),
image,
font,
extra
)
)
const start = series(build, browser)
const deploy = series(build, mydeploy)
module.exports = {
clean,
compile,
build,
serve,
start,
deploy,
lint
}
/*
演示命令:
yarn clean
yarn lint
yarn compile
yarn serve
yarn serve --port 5210 --open
yarn build
yarn build --production
yarn start --port 5210 --open
yarn deploy --branch gh-pages
*/
{
"scripts": {
"clean": "gulp clean",
"compile": "gulp compile",
"serve": "gulp serve",
"build": "gulp build",
"start": "gulp start",
"lint": "gulp lint",
"deploy": "gulp deploy --production"
},
"devDependencies": {
"@babel/core": "^7.10.2",
"@babel/preset-env": "^7.10.2",
"browser-sync": "^2.26.7",
"del": "^5.1.0",
"gulp": "^4.0.2",
"gulp-babel": "^8.0.0",
"gulp-clean-css": "^4.3.0",
"gulp-eslint": "^6.0.0",
"gulp-gh-pages": "^0.5.4",
"gulp-htmlmin": "^5.0.1",
"gulp-if": "^3.0.0",
"gulp-imagemin": "^7.1.0",
"gulp-load-plugins": "^2.0.3",
"gulp-sass": "^4.1.0",
"gulp-sass-lint": "^1.4.0",
"gulp-swig": "^0.9.1",
"gulp-uglify": "^3.0.2",
"gulp-useref": "^4.0.1"
}
}
grunt自动化构建
const sass = require('sass')
const fs = require('fs')
const useref = require('useref')
const loadGruntTasks = require('load-grunt-tasks')
const browserSync = require('browser-sync')
const bs = browserSync.create()
const data = {
menus: [
{
name: 'Home',
icon: 'aperture',
link: 'index.html'
},
{
name: 'Features',
link: 'features.html'
},
{
name: 'About',
link: 'about.html'
},
{
name: 'Contact',
link: '#',
children: [
{
name: 'Twitter',
link: 'https://twitter.com/w_zce'
},
{
name: 'About',
link: 'https://weibo.com/zceme'
},
{
name: 'divider'
},
{
name: 'About',
link: 'https://github.com/zce'
}
]
}
],
pkg: require('./package.json'),
date: new Date()
}
module.exports = grunt => {
grunt.initConfig({
clean: ['dist/**'],
sass: {
options: {
sourceMap: true,
implementation: sass, // implementation指定在grunt-sass中使用哪个模块对sass进行编译,我们使用npm中的sass
},
main: {
files: {
'dist/assets/styles/main.css': 'src/assets/styles/main.scss'
}
}
},
babel: {
options: {
presets: ['@babel/preset-env'],
sourceMap: true
},
main: {
files: {
'dist/assets/scripts/main.js': 'src/assets/scripts/main.js'
}
}
},
web_swig: {
options: {
swigOptions: {
cache: false
},
getData: function (tpl) {
return data;
}
},
main: {
expand: true,
cwd: 'src/',
src: "**/*.html",
dest: "dist/"
},
},
uglify: {
production: {
files: [{
expand: true,
cwd: 'dist/',
src: ['assets/scripts/*.js'],
dest: 'dist/',
}]
},
dev: {}
},
cssmin: {
production: {
files: [{
expand: true,
cwd: 'dist/',
src: ['assets/styles/*.css'],
dest: 'dist/',
}]
},
dev: {}
},
htmlmin: {
production: {
options: {
removeComments: true,
collapseWhitespace: true
},
files: [{
expand: true,
cwd: 'dist/',
src: ['**/*.html'],
dest: 'dist/'
}]
},
dev: {}
},
image: {
production: {
options: {
optipng: false,
pngquant: true,
zopflipng: true,
jpegRecompress: false,
mozjpeg: true,
gifsicle: true,
svgo: true
},
files: [{
expand: true,
cwd: 'dist/',
src: ['assets/fonts/*', 'assets/images/*'],
dest: 'dist/'
}]
},
dev: {}
},
eslint: {
options: {
rulePaths: ['src/assets/scripts/']
},
target: ['src/assets/scripts/main.js']
},
sasslint: {
main: {
options: {
configFile: 'config/.sass-lint.yml',
rulePaths: ['src/assets/scripts/']
},
target: ['src/assets/styles/main.scss']
}
},
copy: {
main: {
files: [{
expand: true,
cwd: 'public/',
src: ['**'],
dest: 'dist/'
},
{
expand: true,
cwd: 'src',
src: ['assets/fonts/*'],
dest: 'dist/'
},
{
expand: true,
cwd: 'src',
src: ['assets/images/*'],
dest: 'dist/'
}
]}
},
watch: {
js: {
files: ['src/js/*.js'],
tasks: ['babel', 'bs-reload']
},
css: {
files: ['src/scss/*.scss'],
tasks: ['sass', 'bs-reload']
},
html: {
files: ['src/**/*.html'],
tasks: ['web_swig', 'bs-reload']
}
},
ghDeploy: {
options: {
repository: 'https://github.com/2604150210/pages-boilerplate-grunt.git',
deployPath: 'dist',
branch: grunt.option('branch') || 'gh-pages',
message: 'Auto deplyment ' + grunt.template.today()
},
}
})
grunt.registerTask("jal-useref", function () {
const done = this.async()
const cwd = 'dist/'
const htmls = ['index.html', 'about.html']
htmls.forEach((html, index) => {
const inputHtml = fs.readFileSync(cwd + html, "utf8")
const [code, result] = useref(inputHtml)
for (let type in result) {
const dests = Object.keys(result[type])
dests.forEach(dest => {
const src = result[type][dest].assets
let read
const files = src.map(file => {
read = cwd + file
if(file[0] === '/') {
read = file.substr(1)
}
return fs.readFileSync(read)
})
fs.writeFile(cwd + dest, files.join(''), (err) => {
if (err) {
return console.error(err);
}
console.log(`${cwd + dest}数据写入${read}成功!`);
})
})
}
fs.writeFile(cwd + html, code, (err) => {
if (err) {
return console.error(err);
}
console.log(`${cwd + html}重写成功!`);
if(index === htmls.length - 1) {
done()
}
})
})
});
// grunt.loadNpmTasks('grunt-sass')
// 启动browserSync
grunt.registerTask("bs", function () {
const done = this.async();
bs.init({
notify: false,
port: grunt.option('port') || 2080,
open: grunt.option('open'),
// files: 'temp/**',
server: {
baseDir: ['dist', 'src', 'public'], // 按顺序查找
routes: {
'/node_modules': 'node_modules'
}
}
}, function (err, bs) {
done();
});
});
grunt.registerTask("bs-reload", function () {
bs.reload()
});
// 获取命令行参数是否含有production或者prod,判断是开发模式还是生产模式
const mode = (grunt.option('production') || grunt.option('prod')) ? 'production': 'development'
loadGruntTasks(grunt) // 自动加载所有的grunt插件中的任务
// 根据命令行参数判断是否需要压缩
grunt.registerTask('mini:production', ['image', 'uglify', 'cssmin', 'htmlmin'])
grunt.registerTask('mini:development', [])
grunt.registerTask('lint', ['sasslint', 'eslint'])
grunt.registerTask('compile', ['sass', 'babel', 'web_swig'])
grunt.registerTask('serve', ['compile', 'bs', 'watch'])
grunt.registerTask('build', ['clean', 'compile', 'copy', 'jal-useref', `mini:${mode}`])
grunt.registerTask('start', ['clean', 'compile', 'copy', 'jal-useref', 'mini:production', 'bs', 'watch'])
grunt.registerTask('deploy', ['clean', 'compile', 'copy', 'jal-useref', 'mini:production', 'ghDeploy'])
}
/*
演示命令:
yarn clean
yarn lint
yarn compile
yarn serve
yarn serve --port=5210 --open
yarn build
yarn build --production
yarn start --port=5210 --open
yarn deploy --branch=gh-pages
*/
{
"scripts": {
"clean": "grunt clean",
"compile": "grunt compile",
"lint": "grunt lint",
"serve": "grunt serve",
"build": "grunt build",
"start": "grunt start",
"deploy": "grunt deploy --production"
},
"devDependencies": {
"@babel/core": "^7.10.2",
"@babel/preset-env": "^7.10.2",
"browser-sync": "^2.26.7",
"concat": "^1.0.3",
"grunt": "^1.1.0",
"grunt-babel": "^8.0.0",
"grunt-browser-sync": "^2.2.0",
"grunt-contrib-clean": "^2.0.0",
"grunt-contrib-concat": "^1.0.1",
"grunt-contrib-copy": "^1.0.0",
"grunt-contrib-csslint": "^2.0.0",
"grunt-contrib-cssmin": "^3.0.0",
"grunt-contrib-htmlmin": "^3.1.0",
"grunt-contrib-jshint": "^2.1.0",
"grunt-contrib-uglify": "^4.0.1",
"grunt-contrib-watch": "^1.1.0",
"grunt-eslint": "^23.0.0",
"grunt-gh-deploy": "^0.1.3",
"grunt-html-build": "^0.7.1",
"grunt-html-template": "^0.1.6",
"grunt-image": "^6.3.0",
"grunt-sass": "^3.1.0",
"grunt-sass-lint": "^0.2.4",
"grunt-scss-lint": "^0.5.0",
"grunt-web-swig": "^0.3.1",
"load-grunt-tasks": "^5.1.0",
"sass": "^1.26.8",
"useref": "^1.4.3"
}
}