# part2-module1 **Repository Path**: ren_wei_1/part2-module1 ## Basic Information - **Project Name**: part2-module1 - **Description**: 提交 - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-08-04 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # part2-module1 ### 简答题 #### 1.谈谈你对工程化的初步认识,结合你之前遇到过的问题说出三个以上工程化能够解决问题或者带来的价值。 > 工程化就是把一些繁琐重复的工作交给系统来自动完成,我们就只需要关注代码的编写,这样可以极大的提升开发效率。 解决的问题: 1.项目的构建部署,可以通过工程化来实现自动化构建部署以及持续集成(不用手动构建部署) 2.模块化和组件化开发(可以提升开发效率) 3.约束团队里面的项目规范 #### 2.你认为脚手架除了为我们创建项目结构,还有什么更深的意义? - 制定项目的规范,这样可以让后期的维护性更好 ### 编程题 #### 1.概述脚手架实现的过程,并使用 NodeJS 完成一个自定义的小型脚手架工具 首先我们需要安装一下依赖 ``` "chalk": "^4.1.0", //用于命令行的显示 "commander": "^6.0.0", //用于自定义自己的命令 "download-git-repo": "^3.0.2", //用于从git仓库上面下载自己的模板 "fs-extra": "^9.0.1", //用于外部文件交互 "handlebars": "^4.7.6", //用于把信息放入模板中 "inquirer": "^7.3.3", //用于命令行交互 "log-symbols": "^4.0.0", //用于美化命令行 "ora": "^4.0.5" //用于增加下载的spinner ``` 首先我们需要创建一个index.js文件,然后我们在index.js文件中写入 ``` #!/usr/bin/env node console.log("hello"); ``` 然后在package.json中写入以下命令 ``` { "name": "react-template", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "bin": { "cli": "./src/index.js" //我们需要在package.json中写入这个bin的信息,当然这个cli命令是可以自定义的 }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "chalk": "^4.1.0", "commander": "^6.0.0", "download-git-repo": "^3.0.2", "fs-extra": "^9.0.1", "handlebars": "^4.7.6", "inquirer": "^7.3.3", "log-symbols": "^4.0.0", "ora": "^4.0.5" } } ``` 然后我们在命令行中执行yarn link 把这个文件链接到全局,之后我们运行cli命令就可以看到打印出来了"hello", 接下来我们需要通过前面的commander来定义我们自己的命令,写在index.js中 ``` program .version(require("../package").version, "-v,-version") //查看版本号的命令 .command("init ") // init name(项目名称) 命令 .action(name => { console.log(name) } ) ``` 通过这个命令,我们就添加了我们自己的两个命令(cli -v ==> 查看cli工具的版本号 cli init name ==> 新建项目的命令(name就是我们的项目名称) ). 在准备进行初始化项目的时候,我们需要对用户进行一些提问,进而生成我们的模板信息,所以我们需要在刚才的action中添加我们inquirer的promot来询问,代码如下 ``` inquirer .prompt([ { type: "input", name: "author", message: "请输入你的名字" }, { type: "input", name: "description", message: "请输入项目描述" } ]) .then(answer => { console.log(answer) }); ``` 在这个代码里面,我们询问了用户当前的author,以及对于项目的描述,然后用户的回答信息都会存在answer中。在用户问题回答完成之后,我们需要去git上面拉取我们自己的模板,然后来生成自己的项目结构,代码如下,并向package.json中写入我们前面填写的信息 ``` //提示正在拉取模板 const process = ora("正在拉取模板..."); process.start(); //从gitee上面下载模板 download( "https://gitee.com:ren_wei_1/react-templates", name, { clone: true }, error => { // console.log(error); if (error) { //如果拉取失败的话 process.fail(); //打印出错误信息 console.log(symbols.error, chalk.red(error)); } else { //拉取成功的时候 process.succeed(); const fileName = `${name}/package.json`; const meta = { name, author: answer.author, description: answer.description }; if (fs.existsSync(fileName)) { //把信息放入package.json中 const content = fs.readFileSync(fileName).toString(); const result = handlebars.compile(content)(meta); fs.writeFileSync(fileName, result); } else { console.log(symbols.error, chalk.red(`${fileName}不存在`)); } console.log(symbols.success, chalk.green("创建成功")); console.log(symbols.info, chalk.green(`cd ${name}`)); console.log(symbols.info, chalk.green(`yarn / npm install`)); console.log( symbols.info, chalk.green(`yarn start / npm run start`) ); } } ); //模板中package.json的内容 "name": "{{name}}", //写入项目名称 "version": "1.0.0", "description": "{{description}}", //写入项目描述 "main": "index.js", "scripts": { "start": "webpack-dev-server --inline --config config/webpack.dev.config.js --open", "test": "echo \"Error: no test specified\" && exit 1", "build": "node script/build.js", "dev": "cross-env NODE_ENV=development webpack-dev-server --colors --config config/webpack.dev.config.js --hot --inline --open", "mock": "cross-env NODE_ENV=mock npm run start" }, "author": "{{author}}", //写入作者 ``` 通过以上代码,我们基本上就构建了一个比较简单的脚手架工具 #### 2.尝试使用 Gulp 完成项目的自动化构建 要构建gulp自动化构建的话,首先我们需要安装一个gulp,然后新建我们的gulpfile.js,然后在这个文件里面来写我们的gulpfile, 接下来,我们先进行我们的构建过程 1. 首先我们解析我们的scss文件,所以我们需要安装gulp-sass和sass来解析我们的css文件,并输出到dist目录下,代码如下 ``` //样式文件的编译和构建 const styles = () => { return ( src("src/assets/styles/*.scss", { base: "src" }) //编译scss的文件并压缩输出 .pipe(sass({ outputStyle: "compressed" })) .pipe(dest(".temp")) ); }; ``` 2. 然后,我们需要通过babel来转换一下我们的js代码,不然很多es6的语法,浏览器是不会识别的,我们需要安装@babel/core,@babel/preset-env依赖,代码如下 ``` //构建js代码 const scripts = () => { return src("src/assets/scripts/*.js", { base: "src" }) //通过babel来进行转换 .pipe(babel({ presets: ["@babel/preset-env"] })) .pipe(dest(".temp")); }; ``` 3. 然后,我们需要把我们的向我们的模板里面添加模板信息,我们需要通过swig来添加信息,并且,我们还需要准备写入模板的数据,代码如下 ``` //填充模板使用的数据 const data = { menus: [ { name: "Home", icon: "aperture", link: "index.html" }, { name: "About", link: "about.html" }, { name: "Contact", link: "#", children: [ { name: "Twitter", link: "https://twitter.com/w_zce" }, { name: "Baidu", link: "https://baidu.com" }, { name: "divider" }, { name: "Github", link: "https://github.com" } ] } ], pkg: require("./package.json"), date: new Date() }; //页面 html的构建任务 const pages = () => { //构建html 并压缩html里面的代码 return src("src/*.html", { base: "src" }) .pipe(swig({ data })) .pipe(dest(".temp")); }; ``` 4. 接下来,我们需要对于图片和字体文件等进行压缩,所以我们需要安装grunt-contrib-imagemin的依赖,代码如下 ``` //构建img的图片等资源 并压缩 const images = () => { return src("src/assets/images/**", { base: "src" }) .pipe(imageMin()) .pipe(dest("dist")); }; //字体文件的构建 并压缩 const fonts = () => { return src("src/assets/fonts/**", { base: "src" }) .pipe(imageMin()) .pipe(dest("dist")); }; ``` 5. 然后还有我们的public下面的文件,这个里面的信息就比较简单了,我们只需要直接把public文件夹下面的内容直接复制过去就可以了,代码如下 ``` //构建public下面的文件 const commons = () => { return src("public/**", { base: "public" }).pipe(dest("dist")); }; ``` 6. 现在整体文件的编译都差不多了,下面我们需要使用browser sync和gulp自带的watch来实现启动浏览器,并根据修改实时更新,代码如下 ``` //开启服务 const server = () => { //监听文件的修改 watch("src/assets/styles/*.scss", styles); watch("src/**/*.html", pages); watch("src/assets/scripts/*.js", scripts); watch(["src/assets/fonts/**", "src/assets/images/**","public/**"], bs.reload); bs.init({ notify: false, //监听文件的改变 files: ".temp/**", server: { //把下面的文件设置为server的文件夹 这样font public还有image等文件 我们在开发的期间不需要转换和压缩 //可以直接使用源文件 baseDir: [".temp", "public", "src"], //把node_modules包含进去 routes: { "/node_modules": "node_modules" } } }); ``` 7. 由于每次开启服务的时候都需要编译一下代码,所以我们把前面的代码编译,所以我们需要把前面的编译整合,并在编译完成之后调用serve命令来启动浏览器 ``` //由于每个文件的编译转换不影响,所以我们可以让这个几个任务同时执行 const compile = parallel(styles, scripts, pages); //由于server命令肯定是要编译完成才能启动,所以我们使用series const serve = series(compile, server); ``` 8. 接下来,我们就要到build代码到生产环境的步骤了,由于要到生产环境,所以我们的js css html肯定都是要压缩,并且有些依赖文件我们也要一并打包到生产环境中,首先,我们先解决html中的一些引用文件的打包,这里我们需要使用gulp-useref依赖,来解决html中引用的js和css,并压缩打包。代码如下 ``` //资源整合 同时压缩代码 const useref = () => { return src(".temp/*.html", { base: ".temp" }) //通过以下目录来寻找依赖的文件 .pipe(useRef({ searchPath: [".temp", "."] })) //当为css的时候进行css压缩 .pipe(gulpIf(/\.*css$/, cleanCss())) //当为js的时候进行js压缩 .pipe(gulpIf(/\.js$/, uglify())) //当为html的时候进行html压缩 .pipe( gulpIf( /\.html$/, htmlmin({ minifyCSS: true, minifyJS: true, removeComments: true, collapseWhitespace: true }) ) ) .pipe(dest("dist")); }; ``` 9. 接下来,我们就可以构建我们的build命令来把整个前端代码进行打包,代码如下 ``` //清除文件夹 const clean = () => { return del(["dist", ".temp"]); }; //打包命令 const build = series( clean, parallel(fonts, images, commons, series(compile, useref)) ); ``` 整体代码如下: ``` // 实现这个项目的构建任务 /** * 导入需要的函数 * src 用于源文件的地址获取 * dest 用于文件导出使用 * series用与同步任务 * parallel用于异步任务 * watch用于监听文件的变化 */ const { src, dest, series, parallel, watch } = require("gulp"); //用于编译sass const sass = require("gulp-sass"); //用于html的压缩 const htmlmin = require("gulp-htmlmin"); //用于js代码的转换 转换成es5等 const babel = require("gulp-babel"); //用于清空文件夹 删除上次的dist const del = require("del"); //图片的压缩 const imageMin = require("gulp-imagemin"); //启动浏览器 const browserAync = require("browser-sync"); //资源合并 const useRef = require("gulp-useref"); //用于进行判断 const gulpIf = require("gulp-if"); //用于压缩js代码 const uglify = require("gulp-uglify"); //用于填充模板数据 const swig = require("gulp-swig"); const cleanCss = require("gulp-clean-css"); const bs = browserAync.create(); //填充模板使用的数据 const data = { menus: [ { name: "Home", icon: "aperture", link: "index.html" }, { name: "About", link: "about.html" }, { name: "Contact", link: "#", children: [ { name: "Twitter", link: "https://twitter.com/w_zce" }, { name: "Baidu", link: "https://baidu.com" }, { name: "divider" }, { name: "Github", link: "https://github.com" } ] } ], pkg: require("./package.json"), date: new Date() }; //构建任务 //样式文件的编译和构建 const styles = () => { return ( src("src/assets/styles/*.scss", { base: "src" }) //编译scss的文件并压缩输出 .pipe(sass({ outputStyle: "compressed" })) .pipe(dest(".temp")) ); }; //页面 html的构建任务 const pages = () => { //构建html 并压缩html里面的代码 return src("src/*.html", { base: "src" }) .pipe(swig({ data })) .pipe(dest(".temp")); }; //构建js代码 const scripts = () => { return src("src/assets/scripts/*.js", { base: "src" }) .pipe(babel({ presets: ["@babel/preset-env"] })) .pipe(dest(".temp")); }; //构建public下面的文件 const commons = () => { return src("public/**", { base: "public" }).pipe(dest("dist")); }; //构建img的图片等资源 并压缩 const images = () => { return src("src/assets/images/**", { base: "src" }) .pipe(imageMin()) .pipe(dest("dist")); }; //字体文件的构建 并压缩 const fonts = () => { return src("src/assets/fonts/**", { base: "src" }) .pipe(imageMin()) .pipe(dest("dist")); }; //资源整合 同时压缩代码 const useref = () => { return src(".temp/*.html", { base: ".temp" }) .pipe(useRef({ searchPath: [".temp", "."] })) .pipe(gulpIf(/\.*css$/, cleanCss())) .pipe(gulpIf(/\.js$/, uglify())) .pipe( gulpIf( /\.html$/, htmlmin({ minifyCSS: true, minifyJS: true, removeComments: true, collapseWhitespace: true }) ) ) .pipe(dest("dist")); }; //清除文件夹 const clean = () => { return del(["dist", ".temp"]); }; //开启服务 const server = () => { //监听文件的修改 watch("src/assets/styles/*.scss", styles); watch("src/**/*.html", pages); watch("src/assets/scripts/*.js", scripts); watch( ["src/assets/fonts/**", "src/assets/images/**", "public/**"], bs.reload ); bs.init({ notify: false, files: ".temp/**", server: { baseDir: [".temp", "public", "src"], //把node_modules包含进去 routes: { "/node_modules": "node_modules" } } }); }; const compile = parallel(styles, scripts, pages); const build = series( clean, parallel(fonts, images, commons, series(compile, useref)) ); const serve = series(compile, server); module.exports = { clean, build, serve }; ``` #### 3.使用 Grunt 完成项目的自动化构建 要使用grunt来构建我们的项目的话,首先我们需要安装grunt,然后在gruntfile.js中来编写我们的配置,整体思路其实和上面gulp相差不大 1. 首先我们需要构建scss文件,所以我们需要安装grunt-sass的依赖,然后我们在grunt.initConfig中添加如下代码 ``` //css的构建 sass: { styles: { //指定用sass来解析 options: { implementation: sass }, files: [ { expand: true, cwd: "src", src: ["assets/styles/*.scss"], dest: ".grunttemp/", ext: ".css" } ] } }, ``` 2. 接下来就是使用babel来转换js代码,这里我们就需要安装grunt-babel @babel/core @babel/preset-env 的依赖,代码如下 ``` //babel转换 babel: { options: { presets: ["@babel/preset-env"] }, scripts: { files: [ { expand: true, cwd: "src", src: ["assets/scripts/*.js"], dest: ".grunttemp/" } ] } }, ``` 3. 下面就是使用grunt-web-swig来填充模板信息,代码如下 ``` // 模板信息拼接 web_swig: { options: { swigOptions: { cache: false }, getData: function(tpl) { return { menus: [ { name: "Home", icon: "aperture", link: "index.html" }, { name: "About", link: "about.html" }, { name: "Contact", link: "#", children: [ { name: "Twitter", link: "https://twitter.com/w_zce" }, { name: "Baidu", link: "https://baidu.com" }, { name: "divider" }, { name: "Github", link: "https://github.com" } ] } ], pkg: require("./package.json"), date: new Date() }; } }, pages: { files: [ { expand: true, cwd: "src", src: ["*.html"], dest: ".grunttemp/" } ] // Target-specific file lists and/or options go here. } }, ``` 4. 下面就是图片和字体文件以及公共文件这些的压缩和复制,这个里面我们需要添加grunt-contrib-imagemin 和 grunt-contrib-copy依赖 ``` //图片压缩 imagemin: { images: { options: { optimizationLevel: 3 }, files: [ { expand: true, cwd: "src", src: ["assets/images/*.{png,jpg,gif,svg}"], dest: "gruntdist/" } ] } }, //把字体公共文件复制过去 copy: { fonts: { files: [ { expand: true, cwd: "src", src: ["assets/fonts/**"], dest: "gruntdist/" } ] }, public: { files: [ { expand: true, cwd: "public", src: ["**"], dest: "gruntdist/" } ] }, //这个是把usemin之后的html文件复制到gruntdist文件夹中 pages: { files: [ { expand: true, cwd: ".grunttemp", src: ["*.html"], dest: "gruntdist/" } ] } }, ``` 5. 接下来,我们就利用grunt-contrib-watch 和 browserSync来实现打开浏览器和文件的监听,具体代码如下 ``` //监听文件的改变 并执行相应的任务来进行编译 watch: { css: { files: ["src/assets/styles/*.scss"], tasks: ["sass"] }, js: { files: ["src/assets/scripts/*.js"], tasks: ["babel"] }, html: { files: ["src/*.html"], tasks: ["web_swig"] }, }, //启动浏览器 browserSync: { //监听改变的文件 bsFiles: { src: ".grunttemp/**" }, options: { watchTask: true, server: { //用于作为server的文件夹地址 baseDir: [".grunttemp", "src", "public"], //把node_modules包含进去Ï routes: { "/node_modules": "node_modules" } } } }, ``` 6. 然后通过registerTask来注册自己的任务 ``` //注册一个编译事件 grunt.registerTask("compile", ["sass", "babel", "web_swig"]); //注册serve事件,编译完成之后,我们可以通过browserSync来启动浏览器和监听 //并通过watch来监听代码的改变并执行相应的task grunt.registerTask("serve", ["compile", "browserSync", "watch"]); ``` 7. 接下来首先,我们需要把html中的代码进行资源整合,把里面引用的js和css进行打包和压缩(需要依赖grunt-usemin,grunt-contrib-uglify,grunt-contrib-cssmin,grunt-contrib-concat),并最后导出到gruntdist目录中 ``` //分析代码中的引用 useminPrepare: { html: [".grunttemp/*.html"], options: { //最终导出目录 dest: "gruntdist", // staging: ".temp", root: [".grunttemp/", "./"] } }, //用绝对路径进行替换 usemin: { html: [".grunttemp/*.html"], options: { assetsDirs: [".grunttemp/", ".", "node_modules"] } }, //压缩css //在这个里面,原本我是打算用通配符的方式来进行压缩,但是会报错,无奈只能使用两个任务来进行了 cssmin: { target: { files: [ { expand: true, cwd: ".tmp/concat", src: ["assets/styles/main.css"], dest: "gruntdist/" } ] }, target2: { files: [ { expand: true, cwd: ".tmp/concat", src: ["assets/styles/vendor.css"], dest: "gruntdist/" } ] } } //我们注册的事件 grunt.registerTask("resource", [ "useminPrepare", "concat:generated", // "cssmin:generated", "uglify:generated", "cssmin:target", "cssmin:target2", "usemin", //把usemin替换过的html拷贝过来 "copy:pages" ]); ``` 整体代码如下 ``` /** * gruntfile文件的编写 */ const sass = require("sass"); module.exports = grunt => { grunt.initConfig({ clean: { temp: ".grunttemp/**", dist: "gruntdist/**", tmp: ".tmp/**" }, //css的构建 sass: { styles: { options: { implementation: sass }, files: [ { expand: true, cwd: "src", src: ["assets/styles/*.scss"], dest: ".grunttemp/", ext: ".css" } ] } }, //babel转换 babel: { options: { presets: ["@babel/preset-env"] }, scripts: { files: [ { expand: true, cwd: "src", src: ["assets/scripts/*.js"], dest: ".grunttemp/" } ] } }, // 模板信息拼接 web_swig: { options: { swigOptions: { cache: false }, getData: function(tpl) { return { menus: [ { name: "Home", icon: "aperture", link: "index.html" }, { name: "About", link: "about.html" }, { name: "Contact", link: "#", children: [ { name: "Twitter", link: "https://twitter.com/w_zce" }, { name: "Baidu", link: "https://baidu.com" }, { name: "divider" }, { name: "Github", link: "https://github.com" } ] } ], pkg: require("./package.json"), date: new Date() }; } }, pages: { files: [ { expand: true, cwd: "src", src: ["*.html"], dest: ".grunttemp/" } ] // Target-specific file lists and/or options go here. } }, //图片压缩 imagemin: { images: { options: { optimizationLevel: 3 }, files: [ { expand: true, cwd: "src", src: ["assets/images/*.{png,jpg,gif,svg}"], dest: "gruntdist/" } ] } }, //把字体公共文件复制过去 copy: { fonts: { files: [ { expand: true, cwd: "src", src: ["assets/fonts/**"], dest: "gruntdist/" } ] }, public: { files: [ { expand: true, cwd: "public", src: ["**"], dest: "gruntdist/" } ] }, pages: { files: [ { expand: true, cwd: ".grunttemp", src: ["*.html"], dest: "gruntdist/" } ] } }, //监听文件的改变 watch: { css: { files: ["src/assets/styles/*.scss"], tasks: ["sass"] }, js: { files: ["src/assets/scripts/*.js"], tasks: ["babel"] }, html: { files: ["src/*.html"], tasks: ["web_swig"] } }, //启动浏览器 browserSync: { bsFiles: { src: ".grunttemp/**" }, options: { watchTask: true, server: { baseDir: [".grunttemp", "src", "public"], routes: { "/node_modules": "node_modules" } } } }, //分析代码中的引用 useminPrepare: { html: [".grunttemp/*.html"], options: { dest: "gruntdist", // staging: ".temp", root: [".grunttemp/", "./"] } }, //用绝对路径进行替换 usemin: { html: [".grunttemp/*.html"], options: { assetsDirs: [".grunttemp/", ".", "node_modules"] } }, //压缩css cssmin: { target: { files: [ { expand: true, cwd: ".tmp/concat", src: ["assets/styles/main.css"], dest: "gruntdist/" } ] }, target2: { files: [ { expand: true, cwd: ".tmp/concat", src: ["assets/styles/vendor.css"], dest: "gruntdist/" } ] } } }); grunt.loadNpmTasks("grunt-contrib-clean"); grunt.loadNpmTasks("grunt-sass"); grunt.loadNpmTasks("grunt-babel"); grunt.loadNpmTasks("grunt-web-swig"); grunt.loadNpmTasks("grunt-contrib-imagemin"); grunt.loadNpmTasks("grunt-contrib-copy"); grunt.loadNpmTasks("grunt-browser-sync"); grunt.loadNpmTasks("grunt-contrib-watch"); grunt.loadNpmTasks("grunt-usemin"); grunt.loadNpmTasks("grunt-contrib-uglify"); grunt.loadNpmTasks("grunt-contrib-cssmin"); grunt.loadNpmTasks("grunt-contrib-concat"); grunt.loadNpmTasks("grunt-useref"); grunt.registerTask("compile", ["sass", "babel", "web_swig"]); grunt.registerTask("serve", ["compile", "browserSync", "watch"]); grunt.registerTask("build", [ "clean", "compile", "imagemin", "copy:fonts", "copy:public", "resource" ]); grunt.registerTask("resource", [ "useminPrepare", "concat:generated", // "cssmin:generated", "uglify:generated", "cssmin:target", "cssmin:target2", "usemin", "copy:pages" ]); // grunt.registerTask("styles", () => {}); }; ```