写在前面
小菜鸡的我又来记录笔记了,这次是前端工程化,感觉现在的前端能做的事情很多,不仅仅是以前写写页面的切图仔了。大到编辑器、页面,小到服务端的增删改都可以去做,而且也不在拘于web端,app、桌面端、服务端都有所涉及。那这次我是学习了webpack方面的知识,总结了一下形成以下笔记~
前端工程化
1、工程化简介
根据业务特点将项目
标准化
模块化
工具化
自动化
前后端分离
它主要包含不同业务场景的技术选型、代码规范、构建发布方案等。主要目地是为了提升前端开发工程师的开发效率与代码质量,降低前后端联调的沟通成本,使得前后端工程师更加专注于自身擅长领域。
2、脚手架
2.1、为什么需要脚手架/脚手架解决的问题
前端在近几年的发展中,已经从简单的静态网页应用,到了现在的桌面端、移动端、服务器端以及复杂的web页面。其中很多项目在创建、编写阶段都面临着
相同的组织结构
相同的开发范式
相同的模块依赖
相同的工具配置
相同的基础代码
基于上述问题,脚手架应运而生。其出现的意义也正是为了解决上述问题。
2.2、yeoman
2.2.1、简介
一句话总结自定义程度较高的老牌脚手架工具,可在项目创建时使用,也可在项目开发中使用(sub Generator)。
2.2.2、使用总结
明确需求,找到合适的generator
全局范围安装generator
通过yo运行generator
通过命令行填写交互选项
生成需要的目录结构
—————————————————————————————————————————————————————————————————
2.2.3、使用已发布的GENERATOR
全局安装
yarn -g add yo
安装generator包
yarn add generator-node (注:yeoman的generator都是generator-xxx的形式)
mkdir xxxcd xxxyo node按照命令行交互输入
2.2.3、自定义GENERATOR
创建项目文件夹, 并且初始化项目
安装yeoman提供的generator,自定义的generator是基于官方的generator实现的。
创建generator/app/index.js
自定义配置后,发布到全局
使用时候,直接yo 自定义generator名字 即可
发布到npm平台
mkdir generator-wc-vuecd generator-wc-vueyarn init code .//phpstorm64 .yarn add yoyarn add yeoman-generator --dev创建generators/app/index.jsyeoman-generator自定义generator如果需要模板的,则创建app/templates,放入内容yarn link //link到全局mkdir wc-vuecd wc-vueyo wc-vue按照命令行交互即可碰到报错的情况可能是gitignore没生成。
注:templates中的文件名字等,需要使用ejs模板的方式等量替换,
如果是模板中碰到<%%>的输入,需要原封不动输出的时候,只要在<%% %>添加一个%即可
//自定义的generator继承官方提供的generator模块生成const Generator = require('yeoman-generator')module.exports = class extends Generator { //命令行交互 prompting() { return this.prompt([ { type: 'input',//输入类型 name: 'name',// key message: 'your project name ',//提示 default: this.appname//默认情况为文件夹名称 } ]) .then(answers => { this.answers = answers //得到交互结果后存储变量,后续使用 }) } writing() { const templates = [ //自定义的模板路径 '.browserslistrc', '.editorconfig', '.env.development', '.env.production', '.eslintrc.js', '.gitignore', 'babel.config.js', 'package.json', 'postcss.config.js', 'README.md', 'public/favicon.ico', 'public/index.html', 'src/App.vue', 'src/main.js', 'src/router.js', 'src/assets/logo.png', 'src/components/HelloWorld.vue', 'src/store/actions.js', 'src/store/getters.js', 'src/store/index.js', 'src/store/mutations.js', 'src/store/state.js', 'src/utils/request.js', 'src/views/About.vue', 'src/views/Home.vue' ] templates.forEach(item => { //遍历路径 this.fs.copyTpl( //输出 this.templatePath(item), // 模板文件路径 this.destinationPath(item),//输出路径 this.answers//将得到的命令行交互作为上下文 ) }) }}
发布到npm平台
如果是第一次发布需要login npm
如果是不是第一次可以直接npm publish
npm login or yarn login碰到输错账户名但npm或者yarn又记忆了的情况下的时候可以yarn logout或者npm logoutnpm publish 即可
2.3、plop
2.3.1、简介
一句话总结,plop是一个小型的生成器,用于在项目中生成特定的目录结构
2.3.2、使用
选择需要使用的文件
安装plop
创建plop入口文件
创建plop模板文件夹
运行plop
cd xxxmkdir plopfile.js配置plopmkdir plop-templateyarn plop
module.exports = function (plop) { // create your generators here 自定义生成器 plop.setGenerator('addComp', { //生成器名称 description: 'this is a addComp', //项目描述 prompts: [//CMD交互 { type:'input', name:'name', //作为key在ejs模板中使用 message:'this is comp name,the first letter must enter upper', default:'Order'//默认输出 } ], // array of inquirer prompts actions: [ { type:'add', //需要的动作 path:'src/components/{{name}}/{{name}}.jsx', //导出路径 templateFile:'plop-template/component.hbs' //模板路径 }, { type:'add', path:'src/components/{{name}}/style.module.scss', templateFile:'plop-template/style.module.scss.hbs' }, ] // array of actions }); };
3、自动化构建工具
3.1、什么是自动化构建工具
前端自动化范围非常广,是很复杂的工程问题,我将其分为三类:
自动化构建(开发)
自动化测试(开发&测试)
自动化发布
其中,自动化构建是将源代码利用工具自动转换成用户可以使用的目标的过程。'目标'在这里指的是js,css,html的集合。'用户'可以是你、测试或者真正的用户。
这些工具使我们能够在更高层次上工作,并自动化重复性任务,从而节省时间;工具简化了我们的工作流程(workflow),使我们可以专注于创造性的工作,而不是花费数小时的时间浪费在繁琐的任务上。例如通过gulp,我们可以让它监听每个源文件的变化,一旦你按下ctrl+s之后,它会自动将新变化内容显示在浏览器中。
这些工具构成的系统,可以称之为构建系统(build system);并且这些工具可以叫做自动化构建工具,其本质是插件或者脚本程序,能代替人去执行繁琐又容易出错的任务。
3.2、为什么要使用自动化构建工具
一句话:自动化。对于需要反复重复的任务,例如压缩(minification)、编译、单元测试、linting等,自动化工具可以减轻你的劳动,简化你的工作。当你在 构建工具正确配置好了任务,任务运行器就会自动帮你或你的小组完成大部分无聊的工作。
3.3、自动化构建工具
3.3.1、GRUNT
1、简介
老牌的自动化构建工具,按照官方的说法。所有你可以想到的重复性的工作在grunt你都可以用插件解决。
2、安装
yarn add grunt
3、使用
type nul>gruntfile.js //配置入口
配置正确选项
yarn grunt 任务名
4、配置
4.1、基础使用和错误抛出
/*TODO: 1、grunt的入口文件 2、用于定义一些需要Grunt自动执行的任务 3、需要导出一个函数 4、此函数接收一个 grunt 的对象类型的形参 5、grunt 对象中提供一些创建任务时会用到的 API*/module.exports=grunt=>{ //注册一个grunt同步任务 grunt.registerTask('foo',()=>{ console.log('hello grunt') }); grunt.registerTask('bar','description',()=>{ console.log('test') }); //default是默认任务的名称,第二个参数可以指定该任务的映射任务 // 这样执行 default 就相当于执行对应的任务 // 这里映射的任务会按顺序依次执行,不会同步执行 grunt.registerTask('default',['foo','bar']) // 也可以在任务函数中执行其他任务 grunt.registerTask('run-other', () => { // foo 和 bar 会在当前任务执行完成过后自动依次执行 grunt.task.run('foo', 'bar') console.log('current task runing~') }) //默认grunt采用同步模式编码 // 如果需要使用异步,可以使用this.async()的方法创建回调 // 由于函数体中需要使用 this,所以这里不能使用箭头函数 grunt.registerTask('async-task',function () { const done=this.async() setTimeout(()=>{ console.log('hello, asyncTask'); done() },1000) }) //———————————————————————————————————————— //抛出错误 grunt.registerTask('wrong',()=>{ console.log('hello ,wrong') return false }) // 异步函数中标记当前任务执行失败的方式是为回调函数指定一个 false 的实参 grunt.registerTask('bad-async', function () { const done = this.async() setTimeout(() => { console.log('async task working~') done(false) }, 1000) }) //错误队列,发生错误就不会依次执行,但是可以在grunt执行的时候加上--force,强制执行 grunt.registerTask('wrongTaskQueue',['foo','wrong','bar'])}
4.2、config配置
module.exports=grunt=>{ // grunt.initConfig() 用于为任务添加一些配置选项 grunt.initConfig({ // 键一般对应任务的名称 // 值可以是任意类型的数据 foo:{ bar:'baz' } }) grunt.registerTask('foo', () => { // 任务中可以使用 grunt.config() 获取配置 console.log(grunt.config('foo')) // 如果属性值是对象的话,config 中可以使用点的方式定位对象中属性的值 console.log(grunt.config('foo.bar')) })}
4.3、多目标
module.exports=grunt=>{ // 多目标模式,可以让任务根据配置形成多个子任务 grunt.initConfig({ build: { options: { msg: 'task options' }, foo: { options: { msg: 'foo target options' } }, bar: '456' } }) grunt.registerMultiTask('build',function () { console.log(this.options()); })}
4.4、常用插件
const sass=require('sass')const loadGruntTasks=require('load-grunt-tasks')module.exports = grunt => { // grunt.initConfig() 用于为任务添加一些配置选项 grunt.initConfig({ // 键一般对应任务的名称 // 值可以是任意类型的数据 //sass->css sass: { options:{ //除配置路径意外还需要添加实施模块。 implementation:sass, sourceMap:true }, main: { files: { //键值对形式,key为输入,value 为输入路径 'dist/css/main.css': 'src/scss/main.scss' } } }, //newEcmaScript=>oldEcmaScript babel:{ options:{ presets:['@babel/preset-env'], sourceMap:true }, main:{ files:{ 'dist/js/app.js':'src/js/app.js' } } }, watch:{ js:{ files:['src/js/*.js'], tasks:['babel'] }, css:{ files:['src/scss/*.scss'], tasks:['sass'] } } }); //loadGruntTasks会自动加载grunt插件中的任务 loadGruntTasks(grunt) // grunt.loadNpmTasks('grunt-sass') //默认任务,编译之后再去监听。 grunt.registerTask('default',['sass','babel','watch'])}
5、使用总结
安装
下载插件
引入插件(注:可以使用load-grunt-tasks自动引入)
配置option
启动grunt
3.3.2 、GULP
1、简介
插件支持度高、可定制化程度高,配置简单的,采用流形式进行读写的自动化构建工具
2、安装
//安装 gulp 命令行工具npm install --global gulp-cli//安装gulp项目依赖npm install --save-dev gulp
3、使用
在使用前,需要知道,gulp中任务默认是异步的,这点和grunt有很大区别。
mkdir gulp-testcd gulp-testtype nul> gulpfile.js //创建gulpfile,作为配置文件入口
4、配置
创建任务
exports.foo=(done)=>{ //默认是异步的,而grunt默认是同步的 console.log('foo task wroking'); done() //手动使用回调,完成任务};//默认taskexports.default=done=>{ console.log( 'end default') done()};//老版本的使用,已经不推荐了,了解即可// v4.0 之前需要通过 gulp.task() 方法注册任务const gulp=require('gulp')gulp.task('bar',done=>{ console.log('old gulp'); done()})
手动抛出错误
const fs=require('fs')exports.callback=done=>{ console.log('callback'); done()}exports.callback_error = done => { console.log('callback task') done(new Error('task failed'))}exports.promise = () => { console.log('promise task') return Promise.resolve()}exports.promise_error = () => { console.log('promise task') return Promise.reject(new Error('task failed'))}const timeout = time => { return new Promise(resolve => { setTimeout(resolve, time) })}exports.async = async () => { await timeout(1000) console.log('async task')}
read、write、pipe
exports.stream = () => { // //读取流 // const read=fs.createReadStream('yarn.lock') // //写入流 // const write=fs.createWriteStream('a.txt') // //通过pipe链接 // read.pipe(write) // //输出流 // return read return src('yarn.lock') .pipe(rename('a.txt')) .pipe(dest('text'))}
此处demo可以使用node自带的fs模块进行读写操作,也可以通过gulp内置的src、pipe、dest进行读写操作,此处需要主要,rename是gulp的插件
yarn add gulp-rename --dev
const rename=require('gulp-rename')
pipe(rename('a.txt'))
src()
创建一个流,用于从文件系统读取 Vinyl 对象。
Vinyl
虚拟的文件格式。当 src()
读取文件时,将生成一个 Vinyl 对象来表示文件——包括路径、内容和其他元数据。
Vinyl 对象可以使用插件进行转换。还可以使用 dest()
将它们持久化到文件系统。
当创建您自己的 Vinyl 对象时——而不是使用 src()
生成——使用外部 vinyl
模块,如下面的用法所示。
dest()
创建一个用于将 Vinyl 对象写入到文件系统的流。
应用demo
// 实现这个项目的构建任务const del = require('del')const browserSync = require('browser-sync')//gulp-load-plugin 自动加载插件const loadPlugins = require('gulp-load-plugins')const plugins = loadPlugins();const {src, dest, parallel, series, watch} = require('gulp')//创建热更新服务器实例const bs = browserSync.create();//返回命令行当前工作目录const cwd=process.cwd();//项目路径的配置let config={ build:{ src:'src', dist:'dist', temp:'temp', public:'public', paths:{ styles:'assets/styles/*.scss', scripts:'assets/scripts/*.js', pages:'*.html', images:'assets/images/**', fonts:'assets/fonts/**', }, }}try{ //读取命令行当前统计目录下的项目基本数据 const loadConfig=require(`${cwd}/pages.config.js`) config=Object.assign({},config,loadConfig)}catch(e){}const clean = () => { //del插件可以配置多目标,用[]包裹即可。 return del([config.build.dist,'temp'])}const style = () => { //按照目录结构输出 return src(config.build.paths.styles, {base: config.build.src,cwd:config.build.src}) .pipe(plugins.sass({outputStyle: 'expanded'})) .pipe(dest(config.build.temp)) .pipe(bs.reload({stream:true})) /* TODO: * 1、yarn add gulp-sass * 2、.pipe(plugins.sass()) * 3、_开头的scss文件在打包的时候会被认为是依赖,直接打包到一起 * */}const script = () => { //yarn add gulp-babel --dev //yarn add @babel/core --dev //yarn add @babel/preset-env --dev return src(config.build.paths.scripts, {base: config.build.src,cwd:config.build.src}) .pipe(plugins.babel({presets: [require('@babel/preset-env')]})) .pipe(dest(config.build.temp)) .pipe(bs.reload({stream:true}))}const page = () => { //yarn add gulp-swig --dev return src(config.build.paths.pages,{base: config.build.src,cwd:config.build.src}) .pipe(plugins.swig({data:config.data})) .pipe(dest(config.build.temp)) .pipe(bs.reload({stream:true}))}const image = () => { return src(config.build.paths.images, {base: config.build.src,cwd:config.build.src}) .pipe(plugins.imagemin()) .pipe(dest(config.build.dist))};const font = () => { return src(config.build.paths.fonts, {base: config.build.src,cwd:config.build.src}) .pipe(plugins.imagemin()) .pipe(dest(config.build.dist))};const extra = () => { return src('**', {base: config.build.public,cwd:config.build.src}) .pipe(dest(config.build.dist))}//yarn add browser-sync--devconst serve = () => { watch(config.build.paths.styles, {cwd:config.build.src},style) watch(config.build.paths.scripts,{cwd:config.build.src}, script) watch(config.build.paths.pages,{cwd:config.build.src}, page) //集中监视,reload watch([ config.build.paths.images, config.build.paths.fonts, ], {cwd:config.build.src},bs.reload) watch('**',{cwd:config.build.public},bs.reload) // watch('src/assets/images/**', image) // watch('src/assets/fonts/**', font) // watch('public/**', extra) bs.init({ //是否连接成功提示 notify: false, // port:2099, // open:false, //监视文件修改 // files: 'dist/**', server: { //网页根目录 baseDir: [config.build.temp,config.build.dist,config.build.public], routes: { //路由配置由于根目录执行 '/node_modules': 'node_modules' } } })}const useref=()=>{ //yarn add gulp-useref --dev return src(config.build.paths.pages,{base:config.build.temp, cwd :config.build.temp}) .pipe(plugins.useref({ searchPath: [config.build.temp, '.'] })) //gulp判断, yarn add gulp-if --dev //yarn add gulp-uglify gulp-clean-css gulp-htmlmin --dev .pipe(plugins.if(/\.js$/, plugins.uglify())) .pipe(plugins.if(/\.css$/, plugins.cleanCss())) .pipe(plugins.if(/\.html$/, plugins.htmlmin({ //压缩html内部的空格、css、js collapseWhitespace: true, minifyCSS: true, minifyJS: true }))) .pipe(dest(config.build.dist))}const compile = parallel(style, script, page)const build = series(clean, parallel(series(compile,useref), font, image, extra))const develop=series(compile,serve)module.exports = { build, develop,clean,useref}
使用
yarn linkcd demoyarn link 'order-gulp' //package.name 自定义type nul>gulpfile.jsmodule.exports=require('orderGulpToNpm')yarn gulp build //抛出的任务都可以调用——————————————————————————————————或者通过npm安装yarn add order-gulp
总结
易于使用:采用代码优于配置策略,Gulp让简单的事情继续简单,复杂的任务变得可管理。
高效:通过利用Node.js强大的流,不需要往磁盘写中间文件,可以更快地完成构建。
高质量:Gulp严格的插件指导方针,确保插件简单并且按你期望的方式工作。
易于学习:通过把API降到最少,你能在很短的时间内学会Gulp。构建工作就像你设想的一样:是一系列流管道。
4、模块化
4.1、模块化规范,ES MODULE注意事项
// 导入成员并不是复制一个副本,// 而是直接导入模块成员的引用地址,// 也就是说 import 得到的变量与 export 导入的变量在内存中是同一块空间。// 导入模块成员变量是只读的// name = 'tom' // 报错// 但是需要注意如果导入的是一个对象,对象的属性读写不受影响// name.xxx = 'xxx' // 正常
5、模块化的实现
5.1、webpack
5.1.1、简介
本质上,
5.1.2、解决了什么问题/作用
生产环境下,尽可能的压缩代码体积,使得项目体积减小,加快渲染速度
开发环境下,使用各种辅助功能,完善开发体验
5.1.3、WEBPACK的基础配置
入口(entry)
输出(output)
loader
插件(plugins)
入口(ENTRY)
入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部
每个依赖项随即被处理,最后输出到称之为
可以通过在 webpack 配置中配置 entry
属性,来指定一个入口起点(或多个入口起点)。默认值为 ./src
。
接下来我们看一个 entry
配置的最简单例子:
webpack.config.js
module.exports = { entry: './path/to/my/entry/file.js'};
出口(OUTPUT)
output 属性告诉 webpack 在哪里输出它所创建的
./dist
。基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。你可以通过在配置中指定一个 output
字段,来配置这些处理过程:webpack.config.js
const path=require('path')module.exports={ // mode:'development', // entry:'./src/index.js',//入口需要是相对路径 output:{ filename:'bundle.js', path:path.join(__dirname,'dist') //出口需要绝对路径 publicPath: 'dist/' //公共目录 }}
LOADER
本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。
注意,loader 能够
import
导入任何类型的模块(例如.css
文件),这是 webpack 特有的功能,其他打包程序或任务执行器的可能并不支持。我们认为这种语言扩展是有很必要的,因为这可以使开发人员创建出更准确的依赖关系图。
在更高层面,在 webpack 的配置中 loader 有两个目标:
test
属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。use
属性,表示进行转换时,应该使用哪个 loader。
css-loader/style-loader
将css作为js代码嵌入
const path = require('path')module.exports = { mode: 'none', entry: './src/main.css',//入口需要是相对路径 output: { filename: 'main.js', path: path.join(__dirname, 'dist') //出口需要绝对路径 }, module: { rules: [ { test: /.css$/, use: [ 'style-loader', 'css-loader',//通过cssloader处理后,再通过styleloader进行引入 ] } ] }}
babel-loader
将es最新特性转换为es5
test: /.js$/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } }},
file-loader/url-loader
file-loader将文件转换为js可导入资源
url-loader将文件转换为data-url的形式
test: /.png$/,use: { loader:'url-loader', //以data-url的方式加载资源,适合小的静态资源 options:{ limit:10 * 1024//超出10kb的情况,采用file-loader,需要注意file-loader必须安装。 } }}
html-loader
处理html内的引入标签
{ test:/.html$/, use:{ loader:'html-loader', options:{ attrs:['img:src','a:href']//html索引,默认只配置了img得src,需要添加资源引入得话就以字符串键值形式添加。 } }},
自定义loader
webpack.config.js
{ test: /.md$/, use: [ 'html-loader',//需要注意得是,解析后的数据必须是js代码,所以此处还需要通过html-loader处理 './markdown-loader' //use类似于require,不仅可以导入三方模块还可以导入本地路径 ]},
maridown-loader
const marked=require('marked')//md语法处理模块。module.exports=source=>{const html=marked(source) return html}
插件(PLUGINS)
loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。
想要使用一个插件,你只需要 require()
它,然后把它添加到 plugins
数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new
操作符来创建它的一个实例。
clean-webpack-plugin
清理输出目录文件,一般用于生产环境
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const webpackConfig = { plugins: [ /** * All files inside webpack's output.path directory will be removed once, but the * directory itself will not be. If using webpack 4+'s default configuration, * everything under <PROJECT_DIR>/dist/ will be removed. * Use cleanOnceBeforeBuildPatterns to override this behavior. * * During rebuilds, all webpack assets that are not used anymore * will be removed automatically. * * See `Options and Defaults` for information */ new CleanWebpackPlugin(), ],};
html-webpack-plugin
自动生成html文件,并且引入相应资源
const path = require('path')const { CleanWebpackPlugin } = require('clean-webpack-plugin')const HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = { mode: 'none', entry: './src/main.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'dist'), // publicPath: 'dist/' //项目根目录,已经通过webpack去自动创建index.html,所以此处不需要指定根目录。 }, ************ plugins: [ // 用于生成 index.html new HtmlWebpackPlugin({ title: 'Webpack Plugin Sample', meta: { viewport: 'width=device-width' }, template: './src/index.html', filename:'1.html' }), // 用于生成 about.html new HtmlWebpackPlugin({ filename: 'about1.html' }) ]}
html-plugin模板
index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body></body> </html>
copy-webpack-plugin
将静态资源原封不动的进行拷贝,一般用在生产环境,此处官方提供的写法存在问题,except array but this is obj。注意即可
const CopyPlugin = require('copy-webpack-plugin')const webpackConfig = { plugins: [ /** * All files inside webpack's output.path directory will be removed once, but the * directory itself will not be. If using webpack 4+'s default configuration, * everything under <PROJECT_DIR>/dist/ will be removed. * Use cleanOnceBeforeBuildPatterns to override this behavior. * * During rebuilds, all webpack assets that are not used anymore * will be removed automatically. * * See `Options and Defaults` for information */ new CopyPlugin([{ from: 'public/**', //带文件夹输出。 }]) /* new CopyPlugin(['public']) //或者直接输出 */ ],};
define-webpack-plugin
在打包的时候提供一个全局变量
const webpack = require('webpack')const webpackConfig = { plugins: [ /** * All files inside webpack's output.path directory will be removed once, but the * directory itself will not be. If using webpack 4+'s default configuration, * everything under <PROJECT_DIR>/dist/ will be removed. * Use cleanOnceBeforeBuildPatterns to override this behavior. * * During rebuilds, all webpack assets that are not used anymore * will be removed automatically. * * See `Options and Defaults` for information */ new webpack.DefinePlugin({ test:JSON.stringify('dsadas')//在打包的时候提供一个全局变量 }) ],};
5.1.4、DEVSERVER
yarn add webpack-dev-server -D
可以用来作为跨域请求的代理器,
devServer: { contentBase: './public', proxy: { '/api': { // http://localhost:8080/api/users -> https://api.github.com/api/users target: 'https://api.github.com', // http://localhost:8080/api/users -> https://api.github.com/users pathRewrite: { '^/api': '' }, // 不能使用 localhost:8080 作为请求 GitHub 的主机名 changeOrigin: true } } }
contentBase静态资源目录 proxy代理器 '/api'以api开头的请求 target代理地址 pathRewrite将api去除 changeOrigin更改主机名
5.1.5、HMR 热拔插
webpack.config.js
const webpack = require('webpack')const HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = { mode: 'development', entry: './src/main.js', output: { filename: 'js/bundle.js' }, devtool: 'source-map', devServer: { hot:true //开启热拔插,提供module.hot // hotOnly: true // 只使用 HMR,不会 fallback 到 live reloading }, plugins: [ new HtmlWebpackPlugin({ title: 'Webpack Tutorial', // template: './src/index.html' }), new webpack.HotModuleReplacementPlugin() //通过webpack自带的插件启动热拔插 ]}
main.js
//work stream.if (module.hot) { let lastEditor = editor module.hot.accept('./editor.js', function (e) { const value=lastEditor.innerHTML document.body.removeChild(lastEditor) const newEditor=createEditor() newEditor.innerHTML=value document.body.appendChild(newEditor) lastEditor=newEditor }) module.hot.accept('./better.png',()=>{ img.src = background console.log(background) })}
5.1.6、DEVTOOL
sourceMap
'cheap-module-eval-source-map',
开发模式一般选用这个,可以定位到行,且可以定位到编译之前的代码
none
生产模式并不需要sourceMap,如果实在需要的话,可以选择'nosources-source-map',可以看到报错信息和列数,但是并未生产sourceMap,防止源代码泄露
5.1.7运行
零配置运行
yarn webpack webpack-cli -Dyarn webpackyarn webpack --mode-production//生产模式yarn webpack --mode-noneyarn webpack --mode-development//开发模式yarn webpack -dev-server //启动devserverwebpack默认以根目录下index.html为入口文件进行打包输出到dist目录输出文件会进行压缩。
5.1.8、MODE
随着项目的越来越大,根据不同的模式进行不同的配置也就变的重要。因此一般的项目我们都会建立
webpack.common.js //dev和prod公共的配置
webpack.dev.js
webpack.prod.js
common
const HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = { entry: './src/main.js', output: { filename: 'js/bundle.js' }, module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.(png|jpe?g|gif)$/, use: { loader: 'file-loader', options: { outputPath: 'img', name: '[name].[ext]' } } } ] }, plugins: [ new HtmlWebpackPlugin({ title: 'Webpack Tutorial', template: './src/index.html' }) ]}
dev
const common=require('./webpack.common')const merge = require('webpack-merge')const webpack=require('webpack')module.exports=merge(common,{ mode:'development', devtool: 'cheap-eval-module-source-map', devServer:{ hot:true, contentBase:'public' }, plugins:[ new webpack.HotModuleReplacementPlugin() ]})
prod
const common=require('./webpack.common')const merge = require('webpack-merge')const webpack=require('webpack')const CopyWebpackPlugin=require('copy-webpack-plugin')const {CleanWebpackPlugin}=require('clean-webpack-plugin')module.exports=merge(common,{ mode:'production', devtool:false, plugins:[ new CleanWebpackPlugin(), new CopyWebpackPlugin(['public']) ]})
进行打包的时候指定对应的文件即可。
也可以在package.json中通过scripts写入
"build": "webpack --config webpack.prod.js","dev": "webpack --config webpack.dev.js"
5.1.9、TREESHAKING
标记未使用或者无用的代码
module.exports = { mode: 'none', entry: './src/index.js', output: { filename: 'bundle.js' }, module: { rules: [ { test: /\.js$/, use: { loader: 'babel-loader', options: { presets: [ '@babel/preset-env' // 如果 Babel 加载模块时已经转换了 ESM,则会导致 Tree Shaking 失效 // ['@babel/preset-env', { modules: 'commonjs' }] // ['@babel/preset-env', { modules: false }] // 也可以使用默认配置,也就是 auto,这样 babel-loader 会自动关闭 ESM 转换 // ['@babel/preset-env', { modules: 'auto' }] ] } } } ] }, optimization: { // 模块只导出被使用的成员 usedExports: true, // 尽可能合并每一个模块到一个函数中 concatenateModules: true, // 压缩输出结果 minimize: true }}
5.1.10、SIDEEFFECT
副作用,无用的模块
webpack.config.js
optimization: { sideEffects: true, //打开sidEffects,同时在package.json中进行标识 // 模块只导出被使用的成员 // usedExports: true, // 尽可能合并每一个模块到一个函数中 // concatenateModules: true, // 压缩输出结果 // minimize: true, }
package.json
"sideEffects": [ "./src/extend.js", "*.css" ] "sideEffects": false 如果确定项目中没有副作用代码,直接全部标识为false即可
副作用模块
import { Button } from './components'// 样式文件属于副作用模块import './global.css'// 副作用模块import './extend'console.log((8).pad(3))document.body.appendChild(Button())
5.1.11、SPLICT-CHUNK
当项目过大的时候,前面提出的尽量将所有的代码合并到一起又变得不适用,因为很可能存在只需要预览a页面,而b页面因为打包到了一起,所以同时进行了加载。
而在实际应用这种资源浪费是需要避免的,因此webpack也就引入了split-chunk
公共的chunk通过optimization.splitChunks可以进行提取
const { CleanWebpackPlugin } = require('clean-webpack-plugin')const HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = { mode: 'none', entry: { //对象形式配置多入口文件 index: './src/index.js', album: './src/album.js' }, output: { //通过占位符的方式,抛出多出口 filename: '[name].bundle.js' }, optimization: { splitChunks: { // 自动提取所有公共模块到单独 bundle chunks: 'all' } }, module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] } ] }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ title: 'Multi Entry', template: './src/index.html', filename: 'index.html', chunks: ['index'] //同时通过chunks配置页面对应的引用 }), new HtmlWebpackPlugin({ title: 'Multi Entry', template: './src/album.html', filename: 'album.html', chunks: ['album'] //同时通过chunks配置页面对应的引用 }) ]}
5.1.12、HASHCHUNK/动态导入
const {CleanWebpackPlugin} = require('clean-webpack-plugin')const HtmlWebpackPlugin = require('html-webpack-plugin')const MiniCssExtractPlugin = require('mini-css-extract-plugin')const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')const TerserWebpackPlugin = require('terser-webpack-plugin')module.exports = { mode: 'none', entry: { main: './src/index.js' }, output: { filename: '[name]-[contenthash:8].bundle.js' }, //生产模式才会开启 optimization: { minimizer: [ //如果在该模式配置了,单独的css压缩,则需要同时配置js压缩,因为开启这个,webpack会认为需要自定义,所以还需要配置js压缩 new TerserWebpackPlugin(), new OptimizeCssAssetsWebpackPlugin() ] }, module: { rules: [ { test: /\.css$/, use: [ // 'style-loader', // 将样式通过 style 标签注入 MiniCssExtractPlugin.loader, 'css-loader' ] } ] }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ title: 'Dynamic import', template: './src/index.html', filename: 'index.html' }), // css模块化 new MiniCssExtractPlugin({ //文件内容发生变化时,生成8位hash filename: '[name]-[contenthash:8].bundle.css' }) ]}
index.js
// import posts from './posts/posts'// import album from './album/album'//静态路径引入的方式,会引起不必要的内存浪费const render = () => { const hash = window.location.hash || '#posts' const mainElement = document.querySelector('.main') mainElement.innerHTML = '' if (hash === '#posts') { // mainElement.appendChild(posts()) //通过动态导入的方式,webpack会自动进行分包和动态引入。 /* webpackChunkName: 'components' */ //通过指定的注释格式,webpack在打包的时候,会生成注释的name //如果name 相同,则会打包到一起 import(/* webpackChunkName: 'components' */'./posts/posts').then(({ default: posts }) => { mainElement.appendChild(posts()) }) } else if (hash === '#album') { // mainElement.appendChild(album()) import(/* webpackChunkName: 'components' */'./album/album').then(({ default: album }) => { mainElement.appendChild(album()) }) console.log(31231) }}render()window.addEventListener('hashchange', render)
5.1.13实现一个VUE脚手架应用的WEBPACK配置
webpack.common
const webpack = require('webpack')var HtmlWebpackPlugin = require('html-webpack-plugin')// 引入html模板模块const StylelintPlugin = require('stylelint-webpack-plugin');// webpack4配置需要包含VueLoaderPlugin,// 否则会报错const VueLoaderPlugin = require('vue-loader/lib/plugin')module.exports = { entry: './src/main.js', // 入口需要是相对路径 optimization: { // 打开sidEffects,同时在package.json中进行标识 // sideEffects: true, splitChunks: { // 自动提取所有公共模块到单独 bundle chunks: 'all' }, // 模块只导出被使用的成员 usedExports: true, // 尽可能合并每一个模块到一个函数中 concatenateModules: true, // 压缩输出结果 minimize: true }, module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', // vue需要通过loader正确解析 options: { transformAssetUrls: { // 转义行内标签 video: ['src', 'poster'], source: 'src', img: 'src', image: ['xlink:href', 'href'], use: ['xlink:href', 'href'] } } }, { test: /\.js$/, exclude: /node_modules/, // node_modules包依赖不需要转换,但仍会被打包,同理include则是处理的时候需要包含。 use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } }, { test: /\.js|.vue$/, exclude: /node_modules/, // node_modules包依赖不需要转换,但仍会被打包,同理include则是处理的时候需要包含。 use: { loader: 'eslint-loader' }, enforce: 'pre' // 需要在js转义之前进行检测 }, { test: /\.(png|jpg|gif)$/i, use: { loader: 'url-loader', // 以data-url的方式加载资源,适合小的静态资源 options: { limit: 10 * 1024, // 超出10kb的情况,采用file-loader,需要注意file-loader必须安装。 esModule: false // 默认开启了,需要关闭 } } }, { test: /\.css$/, // 转成js代码 use: [ 'style-loader', { loader: 'css-loader', options: { sourceMap: true } } ] }, { test: /\.less$/, use: [ 'style-loader', { loader: 'css-loader', options: { sourceMap: true } }, { loader: 'less-loader', options: { lessOptions: { strictMath: true }, sourceMap: true // 也可以开启css的sourcemap,个人感觉没啥用。。开发者工具也可以看到 } } ] } ] }, plugins: [ new HtmlWebpackPlugin({ title: 'wc‘s work ', filename: 'index.html', template: './public/index.html', inject: 'body' // 所有javascript资源将被放置在body元素的底部 }), new VueLoaderPlugin(), new webpack.DefinePlugin({ // BASE_URL:path.join(process.cwd(), 'public/\/') BASE_URL: JSON.stringify('./')// 在打包的时候提供一个全局变量 }), new StylelintPlugin({ files: ['src/*.{vue,html,css,less}'] }) ]}
dev
const common = require('./webpack.common')const merge = require('webpack-merge')const path = require('path')const webpack = require('webpack')module.exports = merge(common, { mode: 'development', devtool: 'cheap-module-eval-source-map', devServer: { contentBase: [path.join(__dirname, 'public'), path.join(__dirname, 'assets')], // 开发环境不拷贝静态文件,以提供基准文件的形式建立正确引入 compress: true, // 所有服务开启gzip port: 9000, // 端口,除常用端口8080等以外均可。 hot: true, // hmr open: true // 直接打开浏览器 }, plugins: [ new webpack.HotModuleReplacementPlugin() // 通过webpack自带的插件启动热拔插 ]})
prod
const common = require('./webpack.common')const CopyPlugin = require('copy-webpack-plugin')const { CleanWebpackPlugin } = require('clean-webpack-plugin')const MiniCssExtractPlugin = require('mini-css-extract-plugin')const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')const TerserWebpackPlugin = require('terser-webpack-plugin')const merge = require('webpack-merge')const path = require('path')const ImageminPlugin = require('imagemin-webpack-plugin').defaultmodule.exports = merge(common, { mode: 'production', output: { // 开启八位hash filename: '[name]-[contenthash:8].bundle.js', path: path.join(__dirname, 'dist') // 出口需要绝对路径 }, optimization: { minimizer: [ // 如果在该模式配置了,单独的css压缩,则需要同时配置js压缩,因为开启这个,webpack会认为需要自定义,所以还需要配置js压缩 new TerserWebpackPlugin(), new OptimizeCssAssetsWebpackPlugin() ] }, module: { rules: [ { test: /\.less$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', 'less-loader' ] } ] }, devtool: 'none', // 生产模式不必开启sourcemap plugins: [ new CleanWebpackPlugin(), // 不同于开发模式,生产环境下需要直接拷贝静态文件 new CopyPlugin({ patterns: [ 'public', 'src/assets' ] }), // css模块化 new MiniCssExtractPlugin({ // 文件内容发生变化时,生成8位hash filename: '[name]-[contenthash:8].bundle.css' }), // 压缩图片 new ImageminPlugin({ pngquant: { quality: '40-50' // 压缩比,直接影响图片质量。 } }) ]})
package.json
{ "name": "vue-app-base", "version": "0.1.0", "private": true, "scripts": { "serve": "webpack-dev-server --mode=development --config webpack.dev.js ", "build": "webpack --mode=production --config webpack.prod.js", "eslintFix": "eslint --ext .js,.html,.vue src --fix", "lint": "webpack --config webpack.common.js" }, "dependencies": { "core-js": "^3.6.5", "vue": "^2.6.11", "webpack": "^4.43.0" }, "devDependencies": { "@babel/core": "^7.10.3", "@babel/preset-env": "^7.10.3", "@modyqyw/stylelint-config-less": "~1.0.0", "@vue/cli-plugin-babel": "^4.4.6", "babel-loader": "^8.1.0", "clean-webpack-plugin": "^3.0.0", "copy-webpack-plugin": "^6.0.2", "css-loader": "^3.6.0", "eslint": "^7.3.1", "eslint-config-standard": "^14.1.1", "eslint-loader": "^4.0.2", "eslint-plugin-import": "^2.21.2", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.1", "eslint-plugin-vue": "^6.2.2", "file-loader": "^6.0.0", "html-webpack-plugin": "^4.3.0", "image-webpack-loader": "^6.0.0", "imagemin-webpack-plugin": "^2.4.2", "less-loader": "^6.1.2", "mini-css-extract-plugin": "^0.9.0", "optimize-css-assets-webpack-plugin": "^5.0.3", "style-loader": "^1.2.1", "stylelint": "^13.6.1", "stylelint-config-standard": "^20.0.0", "stylelint-loader": "^6.2.0", "stylelint-webpack-plugin": "^2.1.0", "terser-webpack-plugin": "^3.0.6", "url-loader": "^4.1.0", "vue-loader": "^15.9.2", "vue-style-loader": "^4.1.2", "vue-template-compiler": "^2.6.11", "webpack-cli": "^3.3.12", "webpack-dev-server": "^3.11.0", "webpack-merge": "^4.2.2" }, "eslintConfig": { "root": true, "env": { "node": true }, "extends": [ "plugin:vue/essential", "eslint:recommended" ], "parserOptions": { "parser": "babel-eslint" }, "rules": {} }, "browserslist": [ "> 1%", "last 2 versions", "not dead" ]}
eslintrc
// eslintConfig和.eslintrc.js文件同时存在的情况下,优先使用本文件module.exports = { env: { browser: true, es2020: true }, extends: [ 'plugin:vue/essential', 'standard' ], parserOptions: { ecmaVersion: 11, sourceType: 'module' }, plugins: [ 'vue' ], rules: { indent: ['error', 4] // 修改为4个空格。。习惯 }}
eslintignore
dist/*!dist/index.js
stylelintrc
module.exports = { extends: [ "stylelint-config-standard" // "@modyqyw/stylelint-config-less" //less 规范,但是存在vue语法冲突问题,取消 ]}
babel
module.exports = { presets: [ '@vue/cli-plugin-babel/preset' ]}
VUE-CLI_手动配置WEBPACK(踩坑记录)
vue-loader: "^16.0.0-beta.4",版本存在问题,建议安装15.2.1
vue-loader-plugin ^1.3.0没有lib ,直接使用
before
// webpack.config.jsconst VueLoaderPlugin = require('vue-loader/lib/plugin') module.exports = { // ... plugins: [ new VueLoaderPlugin() ]}
now
// webpack.config.jsconst VueLoaderPlugin = require('vue-loader-plugin');module.exports = { // ... plugins: [ new VueLoaderPlugin() ]}
VUE-LOADER-TRANSFORMASSETURLS转换规则
资源 URL 转换会遵循如下规则:
如果路径是绝对路径 (例如
/images/foo.png
),会原样保留。如果路径以
.
开头,将会被看作相对的模块依赖,并按照你的本地文件系统上的目录结构进行解析。如果路径以
~
开头,其后的部分将会被看作模块依赖。这意味着你可以用该特性来引用一个 Node 依赖中的资源:<img src="~some-npm-package/foo.png">
如果路径以
@
开头,也会被看作模块依赖。如果你的 webpack 配置中给@
配置了 alias,这就很有用了。所有vue-cli
创建的项目都默认配置了将@
指向/src
。
图片资源 报module.object的错误,是因为错误的使用了esmodule,而这个原因是因为file-loader中的esmodule默认开启
{ test: /\.(png|jpg|gif)$/i, use: { loader: 'file-loader',//以data-url的方式加载资源,适合小的静态资源 options:{ limit:10 * 1024,//超出10kb的情况,采用file-loader,需要注意file-loader必须安装。 esModule:false //默认开启了,需要关闭 } }},
5.2、rollup
5.2.1、简介
Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library 或应用程序。
Rollup 对代码模块使用新的标准化格式,这些标准都包含在 JavaScript 的 ES6 版本中,而不是以前的特殊解决方案,如 CommonJS 和 AMD。ES6 模块可以使你自由、无缝地使用你最喜爱的 library 中那些最有用独立函数,而你的项目不必携带其他未使用的代码。ES6 模块最终还是要由浏览器原生实现,但当前 Rollup 可以使你提前体验。
5.2.2、零配置使用
npm install --dev rollup rollup main.js --file bundle.js --format iife//main.js作为入口文件以iife立即执行函数的形式进行打包,输出到bundle.js
5.2.3、使用配置文件
在项目中创建一个名为 rollup.config.js
的文件,增加如下代码:
// rollup.config.jsexport default { input: 'src/main.js', output: { file: 'bundle.js', format: 'cjs' }};
5.2.4、使用PLUGIN
将 rollup-plugin-json 安装为开发依赖:
npm install --save-dev rollup-plugin-json
(我们用的是 --save-dev
而不是 --save
,因为代码实际执行时不依赖这个插件——只是在打包时使用。)
更新 src/main.js
文件,从 package.json 而非 src/foo.js
中读取数据:
// src/main.jsimport { version } from '../package.json';export default function () { console.log('version ' + version);}
编辑 rollup.config.js
文件,加入 JSON 插件:
// rollup.config.jsimport json from 'rollup-plugin-json';export default { input: 'src/main.js', output: { file: 'bundle.js', format: 'cjs' }, plugins: [ json() ]};
5.2.5、引入NPM模块
rollUp默认只能引入本地相对路径下的模块,而不能像webpack一样直接去使用npm模块
通过插件rollUp也可以达到类似的体验
import json from 'rollup-plugin-json'import resolve from 'rollup-plugin-node-resolve'export default { input: 'src/index.js', output: { file: 'dist/bundle.js', format: 'iife' }, plugins: [ json(), resolve() ]}
5.2.6、打包使用COMMONJS的模块
由于rollup是推荐使用esm标准的,因此rollup本身并不支持commonjs模块,但是许多三方库和模块中可能会使用到commonjs规范,因此
通过rollup-plugin-commonjs插件也可以进行正常打包
import json from 'rollup-plugin-json'import resolve from 'rollup-plugin-node-resolve'import commonjs from 'rollup-plugin-commonjs'export default { input: 'src/index.js', output: { file: 'dist/bundle.js', format: 'iife' }, plugins: [ json(), resolve(), commonjs() ]}
5.2.7、动态导入,模块分割
只需要在文件中使用动态导入的方式,rollup会自动执行split-chunk,需要注意的是,因为代码进行了拆分,就不能以iife的形式进行整合。因为是需要在浏览器端运行,所以选取amd规范
export default { input: 'src/index.js', output: { // file: 'dist/bundle.js', // format: 'iife' dir: 'dist', format: 'amd' }}
5.2.8、多入口打包
export default { // input: ['src/index.js', 'src/album.js'], input: { foo: 'src/index.js', bar: 'src/album.js' }, output: { dir: 'dist', format: 'amd' }}
index.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title></head><body> <!-- AMD 标准格式的输出 bundle 不能直接引用 --> <!-- <script src="foo.js"></script> --> <!-- 需要 Require.js 这样的库 --> <script src="https://unpkg.com/requirejs@2.3.6/require.js" data-main="foo.js"></script></body></html>
5.3、parceljs
🚀 极速打包
Parcel 使用 worker 进程去启用多核编译。同时有文件系统缓存,即使在重启构建后也能快速再编译。
📦 将你所有的资源打包
Parcel 具备开箱即用的对 JS, CSS, HTML, 文件 及更多的支持,而且不需要插件。
🐠 自动转换
如若有需要,Babel, PostCSS, 和PostHTML甚至 node_modules
包会被用于自动转换代码.
✂️ 零配置代码分拆
使用动态 import()
语法, Parcel 将你的输出文件束(bundles)分拆,因此你只需要在初次加载时加载你所需要的代码。
热模块替换
Parcel 无需配置,在开发环境的时候会自动在浏览器内随着你的代码更改而去更新模块。
🚨 友好的错误日志
当遇到错误时,Parcel 会输出 语法高亮的代码片段,帮助你定位问题
了解即可。。
6、代码规范化
6.1、eslint
6.1.1、简介
最为主流的 JavaScript Lint 工具 监测 JS 代码质量
ESLint 很容易统一开发者的编码风格
ESLint 可以帮助开发者提升编码能力
6.1.2、使用
npm i eslint -Dnpx eslint --init //初始化eslint配置npx eslint index.js
6.1.3、常用配置说明
module.exports = { env: { browser: true, //运行环境 es2020: true //语法 }, extends: [ 'standard' //风格 ], parserOptions: { ecmaVersion: 11, //esma规范 sourceType: 'module' //sourceType 有两个值,script 和 module。 对于 ES6+ 的语法和用 import / export 的语法必须用 module. }, rules: { 'no-console': "warn" //自定义规则 }, globals: { "jQuery":"readonly" //全局变量,最好不要使用,即将移除 }}
6.1.4、在GULP工具中的应用
安装
yarn add eslint gulp-eslint -Dyarn eslint --init
gulpfile.js
const script = () => { return src('src/assets/scripts/*.js', { base: 'src' }) .pipe(plugins.eslint()) .pipe(plugins.eslint.format())//在命令行抛出错误 .pipe(plugins.eslint.failAfterError())//终止管道 .pipe(plugins.babel({ presets: ['@babel/preset-env'] })) .pipe(dest('temp')) .pipe(bs.reload({ stream: true }))}
6.1.5、WEBPACK工具中的应用(REACT项目)
yarn add eslint-loader eslint eslint-config-standard -Dyarn eslint --init
eslintrc.js
module.exports = { env: { browser: true, es2020: true }, extends: [ 'standard', 'plugin:react/recommended'//react语法规范 ], parserOptions: { ecmaVersion: 11 }, rules: { // 'react/jsx-uses-react': 2, // 'react/jsx-uses-vars': 2 } // plugins: [ // 'react' // ] }
webpack.config.js
module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: 'babel-loader' }, { test: /\.js$/, exclude: /node_modules/, use: 'eslint-loader', enforce: 'pre' } ] },
6.1.6、在TS项目中的应用
安装
yarn add eslint -Dyarn eslint --init
eslintrc.js
module.exports = { env: { browser: true, es2020: true }, extends: [ 'standard' ], parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 11 }, plugins: [ '@typescript-eslint' ], rules: { }}
6.2、stylelint
6.2.1、简介
检测css/sass/less/postcss等变种css语法的工具
6.2.2、使用
yarn add stylelint -Dyarn add stylelint-config-sass-guidelines -D //scss规范yarn add stylelint-config-standard -D
创建.stylelintrc.js
module.exports = { extends: [ "stylelint-config-standard", "stylelint-config-sass-guidelines" //sass规范 ]}
yarn stylelint *.css
6.3、prettier
6.3.1、简介
Prettier is an opinionated code formatter with support for:
JavaScript, including ES2017
It removes all original styling* and ensures that all outputted code conforms to a consistent style. (See this blog post)
简而言之就是一个代码格式化工具
6.3.2、使用
yarn add prettier -Dyarn prettier index.js --write
6.4、githooks
在代码提交前,如果我们需要对代码进行检测或者其他的操作,我们可以在.git>hooks>pre-commit.sample中自定义
修改文件名pre-commit.sample 为pre-commit
除第一行注释外全部删除,自定义内容
git add . git commit -m ' dasdastest' //即可看到自定义操作
利用上述特性我们可以在提交前进行代码检测
yarn add husky lint-staged -D //husky:将shell语句用json形式去书写 lint-staged:榜致husky完成更多的事
package.json
"scripts": { "test": "eslint ./index.js", "precommit": "lint-staged" },/******/"husky": { "hooks": { "pre-commit": "npm run precommit" } }, "lint-staged": { "*.js": [ "eslint", "git add" ] }
在代码提交前完成eslint检测。
项目发布注意:
发布的模块的时候一定要注意packge.json中的name不能是驼峰命名的。命名为xx-xx-xx最好。
结语
文章中可能会有很多错误,如果出现了错误请大家多多包涵指正(/*手动狗头保命*/),我也会及时修改,希望能和大家一起成长。
下一章,vue底层源码解析。