小菜雞的成長之路(前端工程化)
寫在前面
小菜雞的我又來記錄筆記了,這次是前端工程化,感覺現在的前端能做的事情很多,不僅僅是以前寫寫頁面的切圖仔了。大到編輯器、頁面,小到服務端的增刪改都可以去做,而且也不在拘於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','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'],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,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,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,cwd:config.build.src}) .pipe(plugins.imagemin()) .pipe(dest(config.build.dist))};const font = () => { return src(config.build.paths.fonts,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,script) watch(config.build.paths.pages,page) //集中監視,reload watch([ config.build.paths.images,config.build.paths.fonts,],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、簡介
本質上,
webpack 是一個現代 JavaScript 應用程式的靜態模組打包器(module bundler)。當 webpack 處理應用程式時,它會遞迴地構建一個依賴關係圖(dependency graph),其中包含應用程式需要的每個模組,然後將所有這些模組打包成一個或多個 bundle。5.1.2、解決了什麼問題/作用
生產環境下,儘可能的壓縮程式碼體積,使得專案體積減小,加快渲染速度
開發環境下,使用各種輔助功能,完善開發體驗
5.1.3、WEBPACK的基礎配置
入口(entry)
輸出(output)
loader
外掛(plugins)
入口(ENTRY)
入口起點(entry point)指示 webpack 應該使用哪個模組,來作為構建其內部
依賴圖的開始。進入入口起點後,webpack 會找出有哪些模組和庫是入口起點(直接和間接)依賴的。每個依賴項隨即被處理,最後輸出到稱之為
bundles 的檔案中,我們將在下一章節詳細討論這個過程。可以通過在 webpack 配置中配置 entry
屬性,來指定一個入口起點(或多個入口起點)。預設值為 ./src
。
接下來我們看一個 entry
配置的最簡單例子:
webpack.config.js
module.exports = { entry: './path/to/my/entry/file.js'};複製程式碼
出口(OUTPUT)
output 屬性告訴 webpack 在哪裡輸出它所建立的
bundles,以及如何命名這些檔案,預設值為./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
loader 讓 webpack 能夠去處理那些非 JavaScript 檔案(webpack 自身只理解 JavaScript)。loader 可以將所有型別的檔案轉換為 webpack 能夠處理的有效模組,然後你就可以利用 webpack 的打包能力,對它們進行處理。本質上,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','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,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,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',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',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' } },'css-loader' ] } ] },plugins: [ new CleanWebpackPlugin(),new HtmlWebpackPlugin({ title: 'Multi Entry',filename: 'index.html',chunks: ['index'] //同時通過chunks配置頁面對應的引用 }),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',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$/,// 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$/,options: { sourceMap: true } },{ loader: 'less-loader',options: { lessOptions: { strictMath: true },sourceMap: true // 也可以開啟css的sourcemap,個人感覺沒啥用。。開發者工具也可以看到 } } ] } ] },plugins: [ new HtmlWebpackPlugin({ title: 'wc‘s work ',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,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','dist') // 出口需要絕對路徑 },optimization: { minimizer: [ // 如果在該模式配置了,單獨的css壓縮,則需要同時配置js壓縮,因為開啟這個,webpack會認為需要自定義,所以還需要配置js壓縮 new TerserWebpackPlugin(),module: { rules: [ { test: /\.less$/,use: [ MiniCssExtractPlugin.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',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',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/[email protected]/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$/,use: 'babel-loader' },{ test: /\.js$/,use: 'eslint-loader',enforce: 'pre' } ] },複製程式碼
6.1.6、在TS專案中的應用
安裝
yarn add eslint -Dyarn eslint --init複製程式碼
eslintrc.js
module.exports = { env: { browser: true,extends: [ 'standard' ],parser: '@typescript-eslint/parser',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底層原始碼解析。