前端工程化之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:webappdemo1
esource>npm run hot                    

> resource@1.0.0 hot E:webappdemo1
esource
> 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:webappdemo1
esource
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.
View Code

可以看到访问: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:UsersAdministratorAppDataRoaming
vmv12.16.1
ode_modules
pm
ode_modules
pm-lifecycle
ode-gyp-bin;E:webappdemo1
esource
ode_modules.bin;C:Program Files (x86)Common FilesNetSarang;C:Windowssystem32;C:Windows;C:WindowsSystem32Wbem;C:WindowsSystem32WindowsPowerShellv1.0;C:Program FilesTortoiseSVNin;C:javajdk1.8.0_171in;C:javajdk1.8.0_171jrein;D:apache-maven-3.6.1in;D:apache-maven-3.6.1in;D:Gitcmd;C:Python27;D:mongoDB4.0in;D:mysql-5.6.45-winx64in;C:erl10.5in;C:RabbitMQServer
abbitmq_server-3.7.17sbin;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:UsersAdministratorAppDataRoaming
vm;C:Program Files
odejs;C:Program Files (x86)Yarnin;D:VSCodein;C:UsersAdministratorAppDataLocalGitHubDesktopin;C:UsersAdministratorAppDataRoaming
vm;C:Program Files
odejs;C:UsersAdministratorAppDataLocalYarnin
9 verbose lifecycle resource@1.0.0~hot: CWD: E:webappdemo1
esource
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:UsersAdministratorAppDataRoaming
vmv12.16.1
ode_modules
pm
ode_modules
pm-lifecycleindex.js:332:16)
13 verbose stack     at EventEmitter.emit (events.js:311:20)
13 verbose stack     at ChildProcess.<anonymous> (C:UsersAdministratorAppDataRoaming
vmv12.16.1
ode_modules
pm
ode_modules
pm-lifecyclelibspawn.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:webappdemo1
esource
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 ]
View Code

原因: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

参考:
webpack-dev-server

****几个常用插件*****
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!")
}

工程目录参考:

参考:
webpack官网 
webpack中文文档

原文地址:https://www.cnblogs.com/cac2020/p/13958358.html