Webpack全攻略

2019-09-14  本文已影响0人  Jsonzhang

1. webpack和webpack-cli的安装

cnpm install webpack webpack-cli -g
// webpack -v 检查版本
// webpack-cli -v

全局安装有一个缺点,比如你有一个项目时用webpack4打包的,它依赖的一个项目使用webpack3打包的,这时如果你是全局安装的webpack4,就不能将项目运行起来。怎么办呢?
卸载全局的webpack 和webpack-cli

cnpm uninstall webpack webpack-cli -g

然后我们只在我们的项目中安装需要的版本,进入项目目录,执行:

cnpm install webpack webpack-cli -D
//等价于
cnpm install webpack webpack-cli --save-dev

这时我们在项目目录下执行webpack -v是找不到这个命令,需要用npx webpack -v,这时才会显示版本号。
那我们怎么安装指定的版本呢,以及怎么知道webpack都有哪些版本呢?

cnpm info webpack
image.png

查到现有版本后,我们就可以按需安装了

cnpm install webpack@3.12.0 webpack-cli -D

这样我们就给项目安装了webpack3了

2. 更改webpack默认配置文件

webpack的默认配置文件是webpack.config.js,那么如果我们自定义一个文件叫webpack.js,想让它作为配置文件,怎么办呢?npx webpack --config webpack.js执行这个命令可以改变。

3. 更改默认的命令方式

我们在打包时用的默认命令是npx webpack,我们还可以自定义命令,比如在package.json文件中scripts标签里配置:

"scripts": {
    "build": "webpack"
}

这样我们再运行bundle命令,就会帮我们打包了,命令是npm run build

4. 一个简单的webpack.config.js如下

const path = require('path');
module.exports = {
    mode:'production', //development
    entry:'./src/index.js',
    output:{
        filename:'main.js',
        path:path.resolve(__dirname,'dist');
    }
}
//production 环境输出的是压缩的代码,而development 不压缩

5. 文件打包

我在index.js文件引入一个图片import logo from '.src/logo.png',这是我们再打包,发现报错,因为webpack默认只能打包js文件,其他的文件需要其他的loader来处理。
所以我们安装file-loadercnpm install file-loader -D,然后在webpack.config.js中配置。

const path = require('path');
module.exports = {
    mode:'development', //production
    entry:'./src/index.js',
    output:{
        filename:'main.js',
        path:path.resolve(__dirname,'dist');
    },
    module:{
      rules:[{
        test:/\.png$/,
        use:{
          loader:'file-loader'
        }
      }]
    }
}

6.怎样让打包出的文件名不变

比如我们打包一个logo.png的图片,然后在dist目录下,你发现图片的名字是一长串的字符,那怎样让图片的名字不变呢,我们可以继续在webpack.config.js中配置options。

const path = require('path');
module.exports = {
    mode:'development', //production
    entry:'./src/index.js',
    output:{
        filename:'main.js',
        path:path.resolve(__dirname,'dist');
    },
    module:{
      rules:[{
        test:/\.png$/, //   /\.(png|jpg|gif)$/  多重选择
        use:{
          loader:'file-loader',
          options:{
              name:'[name].[ext]'
          }
        }
      }]
    }
}

7. 怎样让打包出的图片在一个单独文件夹

比如想让打包出的图片在dist/images/目录下,怎么配置呢?

const path = require('path');
module.exports = {
    mode:'development', //production
    entry:'./src/index.js',
    output:{
        filename:'main.js',
        path:path.resolve(__dirname,'dist');
    },
    module:{
      rules:[{
        test:/\.png$/, //   /\.(png|jpg|gif)$/  多重选择
        use:{
          loader:'file-loader',
          options:{
              name:'[name].[ext]',
              outputPath:'/images'
          }
        }
      }]
    }
}

8. 认识url-loader

首先要知道url-loader和file-loader有相同的功能,它也能打包文件。但是它打包的文件是以base64的形式存在js文件中的,这种方式有优点也有缺点:
优点:
如果文件比较小,比如一张图片5kb,打包后这个图片以base64的形式存在js中,就会加载js文件时候,一起把图片加载进来了,省去一次单独的请求。整体加载速度更快了。
缺点:
如果一个文件比较大,那么打包后还是以base64的形式存在js中,就会导致这个js文件比较大,直接导致加载js速度比较慢。

所以如果有一个配置项,比如文件的临界大小是10240字节(10kb),大于这个值我们单独打包出来放到一个文件目录中,比如/images。如果文件大小小于这个值,就直接打包到js文件中,是不是就完美了。

const path = require('path');
module.exports = {
    mode:'development', //production
    entry:'./src/index.js',
    output:{
        filename:'main.js',
        path:path.resolve(__dirname,'dist');
    },
    module:{
      rules:[{
        test:/\.png$/, //   /\.(png|jpg|gif)$/  多重选择
        use:{
          loader:'url-loader',
          options:{
              name:'[name].[ext]',
              outputPath:'/images',
              limit:10240 //大于单独打包到文件,小于以base64打包到js文件
          }
        }
      }]
    }
}

别忘了前提安装url-loadercnpm install url-loader -D

9. 打包css文件

打包css文件,就需要用到style-loader和css-loader
cnpm install style-loader css-loader -D
然后在rules里面加入

{
    test:/\.css$/,
    use:['style-loader','css-loader']
}

10. 打包scss文件

打包scss文件需要用到node-sass和sass-loader
cnpm install node-sass sass-loader -D
然后在rules里面加入

{
    test:/\.scss$/,
    use:['style-loader','css-loader','sass-loader']
}

注意:如果用的多个loader,执行顺序从下到上,从右到左,比如上面的就是先用sass-loader将scss文件解析成css文件,然后用css-loader打包css文件,最后用style-loader将样式挂载到head标签的style标签中。

11.认识postcss-loader

我们在写一些样式的时候要考虑兼容性问题,比如
transform:translate(100px,100px)
这些样式一般都要加厂商前缀来兼容,但是自己写又比较麻烦,有没有一个工具可以帮助我们写,这里就用到postcss-loader,首先还是安装:
cnpm install postcss-loader -D
然后需要新建一个postcss.config.js文件,里面写一些配置:

module.exports = {
    plugins: [
        require('autoprefixer')
    ]
}

这里需要用到一个插件:
cnpm install autoprefixer -D

12. css-loader一些其他配置

{
    test:/\.scss$/,
    use:['style-loader','css-loader','sass-loader','postcss-loader']
}

上面是我们打包的scss文件的配置,打包顺序是postcss-loader先加前缀,sass-loader解析成css,css合并,然后挂载到head中的style标签。
但是如果我一个scss文件中,上面依赖其他的scss文件,比如这样:

@import './a.scss'
body{
  #root{
      width:100px;
      height:100px;
      background:red;
   }
}

如果像上面那样写配置,a.scss文件可能就不会被先post,然后在sass,所以为了让a.scss也走相同的解析顺序。我们要这样配置:

{
  test:/\.scss$/,
  use:[
      'style-loader',
      {
          loader:'css-loader',
          options:{
              importLoaders:2
          }
      },
      'sass-loader',
      'postcss-loader'
   ]
}

还有一个配置,比如一个js文件导入了一个scss文件@important './index.scss',这个js文件的代码就是创建一个div挂载到body上,这个js文件还引用了另一个js文件,里面的代码也是创建一个div。并通过img.classList.add()的方式添加样式,上面的导入方式是全局导入的方式,改变scss中的代码可能会改变很多文件的样式。那么我们想让某个scss文件只作用于某个文件怎么做呢?
首先导入方式更改为:@important style from './index.scss';
然后需要添加css模块化属性:

{
    loader:'css-loader',
    options:{
        importLoaders:2,
        modules:true  //css模块化
    }
}

13. 怎样打包图标字体文件

从iconfont网站找到自己想要的图标,下载下来,把.eot .svg .ttf .woff的字体文件放到font文件夹下,把iconfont.css中的代码拷贝到自己的样式代码中。
<div class='iconfont iconfont-market'></div>
这样就会显示图标了
配置中这样配置:

{
    test:/\.(eot|svg|ttf|woff)$/,
    use:{loader:'file-loader'}
}

14.认识Html-Webpack-Plugin

安装cnpm install html-webpack-plugin -D
作用:HtmlWebpackPlugin会在打包结束后,自动生成一个html文件,并把打包生成的js自动引入到这个html文件中。
进行配置:

const HtmlWebpackPlugin = require('html-webpack-plugin'); //首先要引入

plugins:[
  new HtmlWebpackPlugin()
]

怎样让生成的html文件有固定的元素呢,那就需要一个模板,我们可以在src下新建我们的模板template.html文件,然后做如下配置:

const HtmlWebpackPlugin = require('html-webpack-plugin'); //首先要引入

plugins:[
  new HtmlWebpackPlugin({
    template:'./src/template.html'
  })
]

这样打包生成的html文件就定义的模板html一样了。

15.认识CleanWebpackPlugin

安装:cnpm install clean-webpack-plugin
作用:每次执行打包命令后,都会先将原来打包的dist删除,它不是webpack官方的plugin。

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
plugins: [
        new CleanWebpackPlugin({
            try: false,
            watch: false
        }),
    ]

16. 同一个入口文件,生成不同的文件名

entry:{
    main:'./src/index.js',
    sub:'./src/index.js'
}

output:{
    publicPath:'http://cdn.com.cn', //注入打包后的html中的script标签前缀
    filename:'[name].js',
    path:path.resolve(__dirname,'dist')
}

17.source-map的作用

module.exports = {
    mode:'development', //开发模式下是打开了devtool:source-map的
    devtool:'none', 
    entry:{},
    output:{}
}

当设置devtool:'none'时,如果js中的代码有问题,在控制台显示的位置是在打包后的文件中的,这样定位错误非常不方便,如果我们想让错误定位在原始的js代码中,就需要设置devtool:'source-map'
devtool有很多值可以选,建议开发中使用devtool:'cheap-module-eval-source-map'
这种方式打包速度快,错误提示全。
如果是线上环境建议使用:devtool:'cheap-module-source-map'

18. WebpackDevServer提升开发效率

每次打包都要运行npm run build,然后在到dist目录下打开index.html才能看到改动的效果,过程有点麻烦。那有没有方法,可以监听我的源代码改变,我的源代码一改变,就自动打包,然后我们刷新页面就可以看到效果了。方法有2种:
第一种:改变package.json中的配置:

"scripts": {
   "build": "webpack --watch"
 }

第二种:devserver可以感知我们的源代码改变,并自动打开刷新浏览器
安装:cnpm install webpack-dev-server -D
package.json中加入运行命令:

"scripts": {
   "build": "webpack --watch",
    "devserver":"webpack-dev-server"
 }

webpack.config.js配置:

module.exports = {
    mode:'development', //开发模式下是打开了devtool:source-map的
    entry:{},
    output:{},
    devServer:{
        contentBase:'./dist',
        open:true
    }
}

用devserver解决开发阶段的跨域问题:

 devServer:{
     contentBase:'./dist',
     open:true,
     proxy:{
        '/api':'http://zhangsan.com'
     }
 }

19. 认识HotModuleReplacement 热模块更新

image.png
需求场景:
有一个按钮点击后页面就增加一个div元素,第偶数个元素的背景是有颜色的。这时我把背景色改成蓝色,想看下是否起作用了。由于页面刷新后,现在页面只有一个增加按钮,我需要再点击增加出许多元素,才能发现我改的颜色是否对的。
我的理想状态是页面元素个数不变,只有颜色做更新,也就是我需要的效果更新,其他的不更新。怎么办呢?
这就是要说的热模块更新
首页在webpack.config.js中导入webpack,因为我们一会要用到webpack的一个插件。
cont webpack = require('webpack');
然后在devServer中开启热模块更新:
devServer:{
     contentBase:'./dist',
     open:true,
     proxy:{
        '/api':'http://zhangsan.com'
     },
    hot:true,
    hotOnly:true //可加可不加,加表示热更新可能失效,但是此时浏览器也会帮你更新下,这是hotOnly就是说如果失效就失效,也别刷新。
 }

然后添加热模块更新的插件:

plugins:[
    new webpack.HotModuleReplacementPlugin()
] 

这时我们只对样式文件做更改就可以了。

还有一种情况,比如一个a.js文件导入了b.js 和 c.js文件,b的js代码就是展示一个数,点击自增1。而c的js就展示一个数字。


image.png

这时我点击11,数字开始增大,比如增加到19。然后我去c.js把200改成300,你会发现页面整体刷新,上面数组19回到原始值11。
这就是热更新中的问题,我只想更新下面的数字,不想刷新上面的数字,怎么办呢?
首先上面的热更新配置到配置好后,需要在a.js写如下代码:

import number1 from './b.js'; //里面是一个方法,然后export default 导出
import number2 from './c.js';

number1(); //显示数组11
number2(); //显示数字200

if(module.hot){
    module.hot.accept('./c.js',()=>{
        //处理一些其他逻辑
        //更新
        number2();
    });
}

20. Webpack中用babel处理es6语法

cnpm install babel-loader @babel/core -D
rules中加入下面规则:

{
  test:/\.js$/,
  exclude:/node_modules/,
  loader:'babel-loader'
}

然后在安装:
cnpm install @babel/preset-env -D
babel-loader只是webpack和babel一个桥梁,有了它他们就算认识了,但真正处理es6语法的是babel/preset-env,它里面涵盖了转化es6的一些规则。
然后我们需要再配置:

{
  test:/\.js$/,
  exclude:/node_modules/,
  loader:'babel-loader',
  options:{
    presets:['@babel/preset-env']
  }
}

这样配置之后,一些es6语法可以被转换成es5,比如const转成var,箭头函数转换成普通方法。但是比如new Promise()有些浏览器就没有,我们怎么用es6呢,我们需要用到babel-polyfill来补充这些缺失。
cnpm install @babel/polyfill -D
然后在每个用到es6的js文件顶部引入:import '@babel-polyfill;'
但是这样引入后,打包后你会发现main.js的大小变大了很多,因为babel-polyfill实现了es6中缺失的东西,比如promise,比如arr.map()函数,他会用代码实现一遍。那这样全部实现导致main.js变大,有没有一个方法,让babel-polyfill只实现我文件用到的es6语法,没用到的就不用实现,也就是按需实现,就会大大减小main.js文件的大小。
没错,这是可以实现的,配置如下:

{
  test:/\.js$/,
  exclude:/node_modules/,
  loader:'babel-loader',
  options:{
    presets:[['@babel/preset-env',{
        useBuiltIns:'usage'
    }]]
  }
}

babel/preset-env还有其他许多设置,比如target属性:

{
  test:/\.js$/,
  exclude:/node_modules/,
  loader:'babel-loader',
  options:{
    presets:[['@babel/preset-env',{
        useBuiltIns:'usage',
        targets:{
            chrome:'67'
        }
    }]]
  }
}

这表示我运行的环境是chrome 67的版本,webpack会判断chrome 67已经完全兼容es6语法了,就不会做任何处理了。

有时options里面的内容非常多,我们可以把options的内容单独拿出了,放到一个文件中,我们新建一个文件叫 .babelrc,然后我们把options的内容搬过来:

{
    presets:[['@babel/preset-env',{
        useBuiltIns:'usage',
        targets:{
            chrome:'67'
        }
}

然后webpack.config.js文件里面的内容就剩这样了:

{
  test:/\.js$/,
  exclude:/node_modules/,
  loader:'babel-loader'
}
上一篇下一篇

猜你喜欢

热点阅读