前端工程化之webpack
一、前端工程化
1、什么是前端工程化?
前端工程化是指将前端开发的流程规范化、标准化,包括开发流程、技术选型、代码规范、构建发布等,用于提升前端工程师的开发效率和代码质量。
2、为什么要前端工程化?
复杂度高:前端项目的多功能、多页面、多状态、多系统
规模大:团队开发、多人协作,代码质量管理
要求高:页面性能优化(CDN/异步加载/请求合并),CSS兼容性、单页面应用、服务端渲染...
3、怎么做前端工程化?
从业务着手
简单的单页面应用,使用gulp打包+同步工具实现全流程开发
从复杂度考虑
jenkins、git/gilab、webpack、React/Vue/Angular
从已知向未知扩展
不同的技术有不同的适应场景,选择合适的才是最好的
二、前端构建工具
1、webpack
1.1 安装
#环境确认
nvm install/use v12.16.1 node -v npm -v
#快速创建nodejs项目
npm init -y
#安装webpack webpack-cli
#项目安装 cnpm install webpack webpack-cli -D 或 yarn add webpack webpack-cli -D #全局安装 cnpm install webpack webpack-cli -g 或 yarn add webpack webpack-cli -g
#查看webpack是否安装成功
npx webpack --version
./node_modules/.bin/webpack --version
#webpack使用1
npx webpack XXXX
或
./node_modules/.bin/webpack XXX
#webpack使用2 #构建修改package.json下面scripts/build
{ "name": "resource", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "webpack" }, "author": "", "license": "ISC", "devDependencies": { "webpack": "^5.4.0", "webpack-cli": "^4.2.0" } }
然后控制台直接执行:
npm run build
1.2五大核心概念
(1)入口-entry
单入口webpack配置文件:webpack.config.js
(2)输出-output
path使用绝对路径,可以使用path.join拼接路径,nodejs全局变量__dirname(当前目录的绝对路径)
使用require引入,webstorm需要配置node core
(3)加载器-loader
(3.1)转化css文件
#安装样式loader cnpm install css-loader style-loader -D 或yarn add css-loader style-loader -D
安装完成后,package.json下面的devDependencies会多出两个依赖:
{ "name": "resource", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "webpack" }, "author": "", "license": "ISC", "devDependencies": { "css-loader": "^5.0.1", "style-loader": "^2.0.0", "webpack": "^5.4.0", "webpack-cli": "^4.2.0" } }
#配置使用样式loader打包
const path=require("path") //输出当前目录绝对路径 console.log(path.resolve()) //输出当前目录拼接disk的绝对路径 console.log(path.join(__dirname,'./dist')) const config ={ entry:"./src/index.js", output:{ filename:'bundle.js',//打包后的文件 path:path.join(__dirname,'./dist') }, module:{ rules:[ { test: /\.css$/, use: ['style-loader', 'css-loader'] //执行顺序从右到左 } ] } }; module.exports=config
#编写样式src/index.css
body{
background: red;
}
#index.js引入css
require('./index.scss') console.log("hello webpack!!")
#打包
npm run build
#使用样式 新建./dist/index.html文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> hello cac2020 <script src="bundle.js"></script> </body> </html>
#查看结果
(3.2)一般工程里不会直接编写css样式,而是使用sass、scss预编译css:
#安装sass-loader node-loader
yarn add sass-loader node-sass -D
#webpack设置
const path=require("path") //输出当前目录绝对路径 console.log(path.resolve()) //输出当前目录拼接disk的绝对路径 console.log(path.join(__dirname,'./dist')) const config ={ entry:"./src/index.js", output:{ filename:'bundle.js',//打包后的文件 path:path.join(__dirname,'./dist') }, module:{ rules:[ { test: /\.(scss|sass)$/, use: ['style-loader', 'css-loader','sass-loader'] //执行顺序从右到左 } ] } }; module.exports=config
注意一点:loader执行顺序,从右往左
(4)插件-plugins,提供扩展能力
支柱功能,解决loader无法实现的其他事情。
#插件使用示例 HtmlWebpackPlugin(简单创建 HTML 文件,用于服务器访问)
#安装插件
yarn add html-webpack-plugin -D
#配置中引入
const path=require("path") const Htmlwebpackplugin = require("html-webpack-plugin") const config ={ entry:"./src/index.js", output:{ filename:'bundle.js',//打包后的文件 path:path.join(__dirname,'./dist') }, module:{ rules:[ { test: /\.(scss|sass)$/,//test用来匹配文件 use: ['style-loader', 'css-loader','sass-loader'] //执行顺序从右到左 } ] }, plugins:[ new Htmlwebpackplugin({ filename:"index.html",//生成html的名字 template:'template.html'//使用模板html生成html } )//参数为空 会默认在dist下生成一个普通的index.html ] }; module.exports=config
#打包
npm run build
#效果 自动生成dist/index.html,里面会自动引入bundle.js依赖
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title></head> <body>hello cac2020 <script src="bundle.js"></script> </body> </html>
#代码热编译
方式一:热编译:监控自动触发编译
#修改package.json 监视js是否有变化
{ "name": "resource", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "webpack", "watch": "webpack --watch" }, "author": "", "license": "ISC", "devDependencies": { "css-loader": "^5.0.1", "html-webpack-plugin": "^4.5.0", "node-sass": "^5.0.0", "sass-loader": "^10.1.0", "style-loader": "^2.0.0", "webpack": "^5.4.0", "webpack-cli": "^4.2.0" } }
#执行监控
npm run watch
#修改index.js保存 在控制台会看到自动执行编译日志
... [webpack-cli] watching files for updates... [webpack-cli] Compilation starting... [webpack-cli] Compilation finished ...
#手工刷新页面查看 就会看到更新内容
方式二:热编译+热刷新
使用HMR,模块热替换(HMR - Hot Module Replacement)功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面。
#安装webpack-dev-server,提供开发运行时环境,当有变化时自动刷新浏览器
yarn add webpack-dev-server -D
#配置package.json
{ "name": "resource", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "webpack", "watch": "webpack --watch", "hot": "webpack-dev-server" }, "author": "", "license": "ISC", "devDependencies": { "css-loader": "^5.0.1", "node-sass": "^5.0.0", "sass-loader": "^10.1.0", "style-loader": "^2.0.0", "webpack": "^5.4.0", "webpack-cli": "^4.2.0", "webpack-dev-server": "^3.11.0" } }
#配置webpack.config.js
const path=require("path") const htmlwebpackplugin = require("html-webpack-plugin") const webpack = require('webpack') const config ={ entry:"./src/index.js", output:{ filename:'bundle.js',//打包后的文件 path:path.join(__dirname,'./dist') }, module:{ rules:[ { test: /\.(scss|sass)$/, use: ['style-loader', 'css-loader','sass-loader'] //执行顺序从右到左 } ] }, devServer:{ hot: true }, plugins:[ new htmlwebpackplugin({ filename:"index.html",//生成html的名字 template:'template.html'//使用模板html生成html }),//参数为空 会默认在dist下生成一个普通的index.html new webpack.HotModuleReplacementPlugin()//初始化HMR ] }; module.exports=config
#启动webpack-dev-server
npm run hot
E:\webapp\demo1\resource>npm run hot > resource@1.0.0 hot E:\webapp\demo1\resource > webpack-dev-server i 「wds」: Project is running at http://localhost:8080/ i 「wds」: webpack output is served from / i 「wds」: Content not from webpack is served from E:\webapp\demo1\resource i 「wdm」: Hash: fb2be21415c83bcbde26 Version: webpack 4.35.2 Time: 727ms Built at: 2020-11-12 1:24:03 PM Asset Size Chunks Chunk Names bundle.js 406 KiB main [emitted] main index.html 182 bytes [emitted] Entrypoint main = bundle.js [0] multi (webpack)-dev-server/client?http://localhost (webpack)/hot/dev-server.js ./src/index.js 52 bytes {main} [built] [./node_modules/strip-ansi/index.js] 161 bytes {main} [built] [./node_modules/webpack-dev-server/client/index.js?http://localhost] (webpack)-dev-server/client?http://localhost 4.29 KiB {main} [built] [./node_modules/webpack-dev-server/client/overlay.js] (webpack)-dev-server/client/overlay.js 3.51 KiB {main} [built] [./node_modules/webpack-dev-server/client/socket.js] (webpack)-dev-server/client/socket.js 1.53 KiB {main} [built] [./node_modules/webpack-dev-server/client/utils/createSocketUrl.js] (webpack)-dev-server/client/utils/createSocketUrl.js 2. 77 KiB {main} [built] [./node_modules/webpack-dev-server/client/utils/log.js] (webpack)-dev-server/client/utils/log.js 964 bytes {main} [built] [./node_modules/webpack-dev-server/client/utils/reloadApp.js] (webpack)-dev-server/client/utils/reloadApp.js 1.63 KiB {main } [built] [./node_modules/webpack-dev-server/client/utils/sendMessage.js] (webpack)-dev-server/client/utils/sendMessage.js 402 bytes {main} [built] [./node_modules/webpack/hot sync ^\.\/log$] (webpack)/hot sync nonrecursive ^\.\/log$ 170 bytes {main} [built] [./node_modules/webpack/hot/dev-server.js] (webpack)/hot/dev-server.js 1.59 KiB {main} [built] [./node_modules/webpack/hot/emitter.js] (webpack)/hot/emitter.js 75 bytes {main} [built] [./node_modules/webpack/hot/log-apply-result.js] (webpack)/hot/log-apply-result.js 1.27 KiB {main} [built] [./node_modules/webpack/hot/log.js] (webpack)/hot/log.js 1.34 KiB {main} [built] [./src/index.js] 58 bytes {main} [built] + 24 hidden modules Child HtmlWebpackCompiler: 1 asset Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0 [./node_modules/html-webpack-plugin/lib/loader.js!./template.html] 403 bytes {HtmlWebpackPlugin_0} [built] i 「wdm」: Compiled successfully.
可以看到访问:http://localhost:8080/ 即可查看
执行的时候可能报错:
0 info it worked if it ends with ok 1 verbose cli [ 1 verbose cli 'C:\\Program Files\\nodejs\\node.exe', 1 verbose cli 'C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js', 1 verbose cli 'run', 1 verbose cli 'hot' 1 verbose cli ] 2 info using npm@6.13.4 3 info using node@v12.16.1 4 verbose run-script [ 'prehot', 'hot', 'posthot' ] 5 info lifecycle resource@1.0.0~prehot: resource@1.0.0 6 info lifecycle resource@1.0.0~hot: resource@1.0.0 7 verbose lifecycle resource@1.0.0~hot: unsafe-perm in lifecycle true 8 verbose lifecycle resource@1.0.0~hot: PATH: C:\Users\Administrator\AppData\Roaming\nvm\v12.16.1\node_modules\npm\node_modules\npm-lifecycle\node-gyp-bin;E:\webapp\demo1\resource\node_modules\.bin;C:\Program Files (x86)\Common Files\NetSarang;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files\TortoiseSVN\bin;C:\java\jdk1.8.0_171\bin;C:\java\jdk1.8.0_171\jre\bin;D:\apache-maven-3.6.1\bin;D:\apache-maven-3.6.1\bin;D:\Git\cmd;C:\Python27;D:\mongoDB4.0\bin;D:\mysql-5.6.45-winx64\bin;C:\erl10.5\bin;C:\RabbitMQServer\rabbitmq_server-3.7.17\sbin;D:\gradle-4.6/bin;D:\apache-ant-1.10.7/bin;D:\sonar-scanner-4.2.0.1873/bin;C:\Program Files (x86)\Pandoc\;C:\Users\Administrator\AppData\Roaming\nvm;C:\Program Files\nodejs;C:\Program Files (x86)\Yarn\bin\;D:\VSCode\bin;C:\Users\Administrator\AppData\Local\GitHubDesktop\bin;C:\Users\Administrator\AppData\Roaming\nvm;C:\Program Files\nodejs;C:\Users\Administrator\AppData\Local\Yarn\bin 9 verbose lifecycle resource@1.0.0~hot: CWD: E:\webapp\demo1\resource 10 silly lifecycle resource@1.0.0~hot: Args: [ '/d /s /c', 'webpack-dev-server' ] 11 silly lifecycle resource@1.0.0~hot: Returned: code: 1 signal: null 12 info lifecycle resource@1.0.0~hot: Failed to exec hot script 13 verbose stack Error: resource@1.0.0 hot: `webpack-dev-server` 13 verbose stack Exit status 1 13 verbose stack at EventEmitter.<anonymous> (C:\Users\Administrator\AppData\Roaming\nvm\v12.16.1\node_modules\npm\node_modules\npm-lifecycle\index.js:332:16) 13 verbose stack at EventEmitter.emit (events.js:311:20) 13 verbose stack at ChildProcess.<anonymous> (C:\Users\Administrator\AppData\Roaming\nvm\v12.16.1\node_modules\npm\node_modules\npm-lifecycle\lib\spawn.js:55:14) 13 verbose stack at ChildProcess.emit (events.js:311:20) 13 verbose stack at maybeClose (internal/child_process.js:1021:16) 13 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:286:5) 14 verbose pkgid resource@1.0.0 15 verbose cwd E:\webapp\demo1\resource 16 verbose Windows_NT 6.1.7601 17 verbose argv "C:\\Program Files\\nodejs\\node.exe" "C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js" "run" "hot" 18 verbose node v12.16.1 19 verbose npm v6.13.4 20 error code ELIFECYCLE 21 error errno 1 22 error resource@1.0.0 hot: `webpack-dev-server` 22 error Exit status 1 23 error Failed at the resource@1.0.0 hot script. 23 error This is probably not a problem with npm. There is likely additional logging output above. 24 verbose exit [ 1, true ]
原因:webpack和webpack-dev-server版本不兼容
解决方案:卸载原来版本 下载兼容版本即可
#卸载 yarn remove webpack webpack-cli webpack-dev-server -D 或 cnpm uninstall webpack webpack-cli webpack-dev-server -D #安装指定版本 yarn add webpack@4.35.2 webpack-cli@3.3.5 webpack-dev-server@3.7.2 -D 或 cnpm i webpack@4.35.2 webpack-cli@3.3.5 webpack-dev-server@3.7.2 -D
****几个常用插件*****
clean-webpack-plugin 清理插件
copy-webpack-plugin 拷贝插件
optimize-css-assets-webpack-plugin css压缩插件
terser-webpack-plugin js压缩插件
mini-css-extract-plugin 在html头部单独引用CSS样式文件的压缩插件
适配版本:
yarn add clean-webpack-plugin@3.0.0 copy-webpack-plugin@5.0.3 optimize-css-assets-webpack-plugin terser-webpack-plugin@1.3.0 mini-css-extract-plugin@1.3.0 -D
webpack.config.js:
const path=require("path") const HtmlWebpackPlugin = require("html-webpack-plugin") const webpack = require('webpack') //清理插件的定义比较特殊 使用{} 前后有空格 const { CleanWebpackPlugin } = require('clean-webpack-plugin') const CopyWebpackPlugin = require('copy-webpack-plugin') const MiniCssExtractPlugin = require('mini-css-extract-plugin') const TerserJSPlugin = require('terser-webpack-plugin') const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin') const config ={ mode:'production', entry:"./src/index.js", output:{ filename:'bundle.js',//打包后的文件 path:path.join(__dirname,'./dist') }, //optimization 压缩配置 optimization:{ minimizer:[new TerserJSPlugin({}),new OptimizeCSSAssetsPlugin({})], }, module:{ rules:[ { test: /\.(scss|sass)$/, use: [MiniCssExtractPlugin.loader, 'css-loader','sass-loader'] //执行顺序从右到左 }, { test:/\.js$/, loader:'babel-loader' } ] }, devServer:{ hot: true }, plugins:[ new HtmlWebpackPlugin({ filename:"index.html",//生成html的名字 template:'template.html'//使用模板html生成html }),//参数为空 会默认在dist下生成一个普通的index.html new webpack.HotModuleReplacementPlugin(),//初始化HMR new CleanWebpackPlugin(), new CopyWebpackPlugin([ { from: path.join(__dirname, 'assets'), to: 'assets' } ]), //MiniCssExtractPlugin会把样式单独提取出来放到一个css文件被html引用 new MiniCssExtractPlugin({ filename:'[name].css', chunkFilename:'[id].css' }) ] }; module.exports=config
(5)模式/兼容性
模式-mode:将process.env.NODE_ENV设置为开发模式-development和产品模式production,不同模式webpack会启用不同的插件,比如production模式下会启用压缩、混淆插件。
#webpack.config.js配置:
const path=require("path") const htmlwebpackplugin = require("html-webpack-plugin") const webpack = require('webpack') const config ={ mode:'development', entry:"./src/index.js", output:{ filename:'bundle.js',//打包后的文件 path:path.join(__dirname,'./dist') }, module:{ rules:[ { test: /\.(scss|sass)$/, use: ['style-loader', 'css-loader','sass-loader'] //执行顺序从右到左 } ] }, devServer:{ hot: true }, plugins:[ new htmlwebpackplugin({ filename:"index.html",//生成html的名字 template:'template.html'//使用模板html生成html }),//参数为空 会默认在dist下生成一个普通的index.html new webpack.HotModuleReplacementPlugin()//初始化HMR ] }; module.exports=config
#process.env.NODE_ENV的使用
#修改index.js
require('./index.scss') console.log("hello webpack!! Ok") if(process.env.NODE_ENV == 'development'){ console.log("baseurl is localhost!") } else{ console.log("baseurl is cac2020.com!") }
loader与plugins区别:
loader去转化webpack不能处理的文件类型,也就是除了js以外的文件,转化为webpack能够处理的文件。
plugins;扩展更复杂的构建任务功能。
1.3 babel
Babel是一个JavaScript编译器,可以将最新版的javascript编译成当下可以执行的版本,简言之,利用babel就可以让我们在当前的项目中随意的使用这些新最新的es6,甚至es7的语法。说白了就是把各种javascript千奇百怪的语言统统专为浏览器可以认识的语言。
#安装babel
yarn add babel-loader@8.0.6 @babel/core@7.5.0 @babel/preset-env@7.5.0 @babel/plugin-transform-runtime@7.5.0 -D
#在生产环境中提供babel运行时
yarn add @babel/runtime@7.5.1 -S
#新增babel配置文件 .babelrc 配置babel转换运行时插件等
{ "presets": [ "@babel/preset-env" ], "plugins": [ "@babel/plugin-transform-runtime" ] }
#编辑配置文件webpack.config.js
const path=require("path") const HtmlWebpackPlugin = require("html-webpack-plugin") const webpack = require('webpack') //清理插件的定义比较特殊 使用{} 前后有空格 const { CleanWebpackPlugin } = require('clean-webpack-plugin') const CopyWebpackPlugin = require('copy-webpack-plugin') const MiniCssExtractPlugin = require('mini-css-extract-plugin') const TerserJSPlugin = require('terser-webpack-plugin') const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin') const config ={ mode:'production', entry:"./src/index.js", output:{ filename:'bundle.js',//打包后的文件 path:path.join(__dirname,'./dist') }, //optimization 压缩配置 optimization:{ minimizer:[new TerserJSPlugin({}),new OptimizeCSSAssetsPlugin({})], }, module:{ rules:[ { test: /\.(scss|sass)$/, use: [MiniCssExtractPlugin.loader, 'css-loader','sass-loader'] //执行顺序从右到左 }, { test:/\.js$/, loader:'babel-loader' } ] }, devServer:{ hot: true }, plugins:[ new HtmlWebpackPlugin({ filename:"index.html",//生成html的名字 template:'template.html'//使用模板html生成html }),//参数为空 会默认在dist下生成一个普通的index.html new webpack.HotModuleReplacementPlugin(),//初始化HMR new CleanWebpackPlugin(), new CopyWebpackPlugin([ { from: path.join(__dirname, 'assets'), to: 'assets' } ]), //MiniCssExtractPlugin会把样式单独提取出来放到一个css文件被html引用 new MiniCssExtractPlugin({ filename:'[name].css', chunkFilename:'[id].css' }) ] }; module.exports=config
#写一个ES6语法的a.js
export default ()=> { console.log('hello from module a') }
#在index.js引用
import('./index.scss') //从a.js引入afn import afn from './a' //执行afn afn() console.log("hello webpack!! Ok") if(process.env.NODE_ENV == 'development'){ console.log("baseurl is localhost!") } else{ console.log("baseurl is cac2020.com!") }
工程目录参考: