大前端學習--使用node、grunt、gulp實現前端項目工程化(含視頻講解)

使用node、grunt、gulp實現前端項目工程化

我把熬夜錄製的講解視頻也放在下面了,本寶寶是不是很貼心😄😄😄😄😄😄😄😄😄😄😄😄

1. 概述腳手架實現的過程,並使用NodeJS完成一個自定義的小型腳手架工具

腳手架的實現過程就是在啓動腳手架之後,自動地去詢問一些預設問題,通過回答的結果結合一些模板文件,生成項目的結構。

使用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如下:

    <!DOCTYPE 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命令,就可以根據模板自動化創建文件了

2. 嘗試使用Gulp完成項目的自動化構建

視頻演示地址:

gulp講解

gulpfile.js

// 實現這個項目的構建任務
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
*/

package.json的部分內容

{
 "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"
  }
}

3. 使用Grunt完成項目的自動化構建

視頻演示地址:

grunt自動化構建

gruntfile.js

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
*/

package.json的部分內容

{
 "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"
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章