>>建立nodejs工程
新建文件夹,npm init 生成package.json
>>安装webpack 和 webpack-dev-server
npm install --save-dev webpack@3.8.1 注意4.x版本语法有些变化
npm install --save-dev webpack-dev-server@2.9.7 注意踩坑记录1
>>安装babel转码es6
Babel其实是几个模块化的包,其核心功能位于称为babel-core
的npm包中,webpack可以把其不同的包整合在一起使用,对于每一个你需要的功能或拓展,你都需要安装单独的包(用得最多的是解析Es6的babel-env-preset
包和解析JSX的babel-preset-react
包)。
babel 6 与 bable-loader 7匹配,
另外解决promise等,用 babel-polyfill,bebel6对应版本为babel-polyfill 6,解决组件按需加载编译,用 babel-plugin-import
>>支持 react 开发
npm install --save-dev react react-dom 注意这里是本地安装,也可以用全局安装
安装其他可选插件:
>>配置webpack.config.js
踩坑记录
1:webpack是3.x版本的,webpack-dev-server是3.x的版本,这两个版本不兼容,可以把webpack-dev-server降到2.x版本
踩坑解决办法示例:TypeError: Cannot read property 'compile' of undefined #1334
解决思路: 优先使用 Google
引擎进行搜索关键词句, 比如 webpack Cannot read property 'compile' of undefined
;看能否找到相应的问题。
如果不行,不妨换一种方式再搜索,譬如:site:*.com webpack Cannot read property 'compile' of undefined
,在具体某个网站下搜索;如果还是没能找见解决办法的话,可以在各种平台提问,比如 segmentfault。
额外补充: 对于 Google
这个工具还真是有必要先学,具体常用操作可参见:如何更好地运用 Chrome (Google)。倘若,不能够没有适用 Google
的环境,那么这里整理集结若干优质搜索引擎,堪称 Google 搜索优质替代品,可供参考。
package.json 示例:
{
"name": "webpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config webpack.config.js --watch"
},
"author": "",
"license": "ISC",
"devDependencies": {
"antd": "^3.25.0",
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-plugin-import": "^1.12.2",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"clean-webpack-plugin": "^1.0.0",
"copy-webpack-plugin": "^4.6.0",
"css-loader": "^3.2.0",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^4.2.0",
"html-webpack-plugin": "^3.2.0",
"less-loader": "^5.0.0",
"sass-loader": "^7.3.1",
"style-loader": "^1.0.0",
"uglifyjs-webpack-plugin": "^1.3.0",
"url-loader": "^2.1.0",
"webpack": "^3.8.1",
"webpack-bundle-analyzer": "^3.1.0",
"webpack-dev-server": "^2.9.7"
},
"dependencies": {
"react": "^16.9.0",
"react-dom": "^16.9.0"
}
}
webpack.config.js 示例:
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const CleanWebpackPlugin = require("clean-webpack-plugin");
// const ExtractTextPlugin = require("extract-text-webpack-plugin");
// const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); module.exports = {
// context: path.resolve(__dirname), //webpack运行时的根目录,默认为当前目录 // 配置寻找模块的规则
resolve: {
modules: [ // 寻找模块的根目录,array 类型,默认以 node_modules 为根目录
path.resolve(__dirname),
'node_modules'
],
extensions: ['.js', '.jsx', '.json', '.css', '.less', '.scss'], // 当模块没有后缀名时,webpack尝试添加后查找文件,常用的放前面
alias: { // 别名,用于替换import导入模块的路径,例如可在dev环境导入不压缩的包,prd环境导入压缩的包
"react": "react/cjs/react.production.min.js", // 将import react替换为import "react.min.js"
"react-dom": "react-dom/cjs/react-dom.production.min.js",
// "LP": "xxx/lp.min.js", // 将"LP"替换为xxx/lp.min.js
// 'only-module$': 'new-module'
// 使用结尾符号 $ 后,只把 'only-module'结尾的路径 映射成 'new-module',
// 'module/path/file' 不会被映射成 'new-module/path/file'
},
enforceExtension: false, // 是否强制导入语句必须要写明文件后缀
}, // entry 表示入口文件,注意这里的路径拼上resolve.modules路径即尝试加载入口文件的完整路径
// 类型可以是 string | object | array
// entry: "src/js/entry.js", // 只有1个入口,入口只有1个文件,对应1个chunk出口文件,且命名为main
// entry: ["src/js/entry1.js", "src/js/entry2.js"], // 只有1个入口,入口有2个文件,对应1个chunk出口文件,且命名为main
entry: { // 有2个入口,对应2个chunk出口文件。entry为object时对应多个chunk出口文件,名称默认为key值。
// 'vendor': ['babel-polyfill', 'isomorphic-fetch', 'prop-types', 'react', 'react-dom', 'react-redux', 'react-router-dom', 'redux', 'react-bootstrap'],
'vendor': ['babel-polyfill', 'react', 'react-dom'],
"index": "src/app/index.js"
},
// entry: ()=>{ // 可由函数动态生成entry
// return {
// a:"xxx",
// b:"yyy"
// }
// } // resolveLoader: { // 告诉 webpack 如何寻找Loader源文件,可用于加载本地的Loader
// modules: ['node_modules'], // 去哪个目录寻找
// extensions: ['.js', 'json'], // 入口文件后缀
// mainFields: ['loader', 'main'] // 优先使用哪种类型的入口,可不配置
// }, externals: { // 告诉webpack要构建的代码中使用了哪些不用打包的模块(通常直接在模板html的script标签中引入),即直接使用JS运行环境提供的全局变量
jquery: 'window.jQuery' // 将导入语句里的jqurey替换成全局变量 window.jQuery。例如代码中有 import $ from 'jquery' 会被正确的替换
}, // 一般代码中也不会用模块引入的语法引入本身不打包的模块,因此这一项也可以不配置 // 如何输出结果:在 Webpack 经过一系列处理后,如何输出最终想要的代码。
output: {
// 输出文件存放的目录,必须是 string 类型的绝对路径。
path: path.resolve(__dirname, 'dist'),
// 引用资源的路径 URL 前缀,拼上entry的路径即为资源访问路径。
// publicPath: 'https://cdn.example.com/', // 放到 CDN 上去,html中引入js时使用https://cdn.example.com/xxx.js
// 通常本地调试和发布到线上时访问路径不同,可以根据参数化选择:
// publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath // 输出文件的名称
// filename: 'bundle.js', // 完整的名称
// filename: '[name].js', // 当配置了多个 entry 时,通过名称模版为不同的 entry 生成不同的文件名称
filename: '[name].[chunkHash:8].js', // 根据文件内容 hash 值取8位生成文件名称,用于浏览器长时间缓存文件 // 导出库的名称,string 类型
// 不填它时,默认输出格式是匿名的立即执行函数
// library: 'MyLibrary', // 导出库的类型,枚举类型,默认是 var
// 可以是 umd | umd2 | commonjs2 | commonjs | amd | this | var | assign | window | global | jsonp ,
// libraryTarget: 'umd', // 附加 Chunk 的文件名称,用于指定无入口、动态加载、webpack运行过程中生成的chunk的名称,通常也可以在具体的plugin中去配置
// chunkFilename: '[id].js',
// chunkFilename: '[chunkhash].js'
}, // 配置模块相关
module: {
rules: [ // 配置 Loader
{
test: /\.jsx?$/, // 正则匹配命中要使用该 Loader 的文件,可以为字符串或正则,或者数组类型
// include: [ // 匹配范围包含,即只会命中这里面的文件
// path.resolve(__dirname, 'src')
// ],
exclude: /node_modules/, // 匹配范围不包含,即排除这里面的文件
use: [{ // 使用哪些 Loader,有多个时从后往前执行,可以通过options传一些参数
loader: "babel-loader"
}],
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'] // 不传参数时可以简写
// 注:style-loader作用是将css编译到js中,然后运行时插入DOM节点的style属性,可能导致js代码较大,且css复用性不够。可用ExtractTextWebpackPlugin优化。
// use: ExtractTextPlugin.extract({
// fallback: "style-loader", // 编译后用什么loader来提取css文件并引入到html
// use: "css-loader"
// })
},
{
test: /\.less$/,
use:['style-loader','css-loader', 'less-loader']
},
{
test: /\.scss$/,
use:['style-loader','css-loader', 'sass-loader']
},
{
// 对小图片或字体文件直接编码为base64提高加载速度,当图片超过限制时直接使用file-loader拷贝
test: /\.(png|jpe?g|gif|svg|eot|woff2?|ttf|pdf)$/,
loader: 'url-loader',
include: [path.resolve(__dirname, 'src')],
options: {
limit: 10000, // 小于10kb的才编码为base64,建议10kb量级的小文件才编码
name: 'media/[name].[hash:7].[ext]'
// 另外还可以配置publicPath等参数,参见文末说明
}
},
],
noParse: [ // 不用解析和处理的模块,如jQuery等非模块化的库,被忽略的模块不能包含import require define等模块化语句
/jquery|chartjs/ // 用正则匹配
],
// noParse: (path) => {
// return /jquery|chartjs/.test(path);
// }
}, plugins: [
new CleanWebpackPlugin(['dist']),
new CopyWebpackPlugin([{
from: __dirname+'/src/resource', // 静态资源目录地址
to: __dirname+'/dist/resource',
ignore: ["test.html"]
}]),
// new ExtractTextPlugin({ // 提取js的css到文件,参见文末说明
// filename: 'css/style.[contenthash:16].css',
// }),
new webpack.optimize.CommonsChunkPlugin({ // 从vendor chunk中提取第三方公共模块,然后从中提取webpack运行文件到runtime
name: ['vendor','runtime'],
filename: '[name].[hash:7].js',
minChunks: 2 // 公共模块被引用几次时被提取出来
}),
// new webpack.optimize.CommonsChunkPlugin({ // 提取自定义公共模块
// name: 'common',
// filename: '[name].[hash:7].js',
// chunks: ['first','second']//从first和second chunk中抽取commons chunk
// }),
// new UglifyJsPlugin(),
new HtmlWebpackPlugin({
title: "首页", // 指定页面标题
favicon: "", // 指定页面图标
template: __dirname + "/src/template/index.html", // html模板文件路径
inject: true, // true|head|body|false,当传入 true或者 body时所有js模块将被放置在body元素的底部,head时则会放在head元素内
// minify:{ // 压缩HTML文件
// removeComments:true, // 移除HTML中的注释
// collapseWhitespace:true // 删除空白符与换行符
// },
filename: "index.html", // 输出html文件路径
chunks: [ // 向html script标签中添加哪些chunk
"runtime", // webpack运行文件要排在最前面优先加载
"vendor",
"index"
],
// excludeChunks: [ // 被排除的模块
// "main"
// ]
chunksSortMode: 'manual', // 设置chunk插入到html的script标签的顺序, none|auto|dependency|manual|{function} 默认为'auto,常用manual根据chunks的位置手动排序
}),
],
devServer: {
contentBase: path.join(__dirname, "dist"), //本地服务器所加载的页面所在的目录
publicPath: "/dist/", // 和output.publicPath作用一样,本地调试用,会覆盖output.publicPath的值,确保以/开头以/结尾
inline: true, // 实时刷新
// hot: true, // 是否开启模块热替换功能(不重新加载整个网页的情况下刷新模块),默认关闭
port: 9000, // 端口改为9000
compress: true, // 是否开启 gzip 压缩
https: false, // 是否开启 HTTPS 模式
// open:true // 自动打开浏览器
// headers: { // response中注入一些响应头
// 'X-foo': '112233'
// },
// historyApiFallback: true, // 404时定向跳转,一应用在HTML5 History API 的单页应用,比如访问不到路由时跳转到index.html
// 还可以配置proxy 实现跨域请求,参见文末
},
// http://localhost:9000/webpack-dev-server 可以查看运行时dev-server在内存中生成的目录结构 // 其他配置
devtool: 'source-map', // 配置 source-map 类型,方便调试时查看源码
profile: false, // 是否捕捉 Webpack 构建的性能信息,用于分析什么原因导致构建性能不佳
cache: true, // 是否启用缓存提升构建速度
watch: true, // 是否开启监听模式,在文件变化时自动重新编译(默认否,当使用devServer时默认开启)
watchOptions: { // 监听模式选项
// 不监听的文件或文件夹,支持正则匹配。默认为空
ignored: /node_modules/,
// 监听到变化发生后会等待多长时间再去执行编译,防止文件更新太快导致重新编译频率太高,默认为300ms
aggregateTimeout: 300,
// 检测文件是否发生变化的间隔时长,默认每隔1000毫秒询问一次
poll: 1000
}, // 输出文件性能检查配置
performance: {
hints: 'warning', // 有性能问题时输出警告
hints: 'error', // 有性能问题时输出错误
hints: false, // 关闭性能检查
maxAssetSize: 200000, // 最大文件大小 (单位 bytes)
maxEntrypointSize: 400000, // 最大入口文件大小 (单位 bytes)
assetFilter: function(assetFilename) { // 过滤要检查的文件
return assetFilename.endsWith('.css') || assetFilename.endsWith('.js');
}
}, stats: { // 控制台输出日志控制
assets: true,
colors: true,
errors: true,
errorDetails: true,
hash: true,
} }; // node中的路径
// __dirname: 总是返回被执行的 js 所在文件夹的绝对路径
// __filename: 总是返回被执行的 js 的绝对路径
// process.cwd(): 总是返回运行 node 命令时所在的文件夹的绝对路径 /*
devServer proxy 实现跨域 有时候我们使用webpack在本地启动服务器的时候,由于我们使用的访问的域名是 http://localhost:8081 这样的,但是我们服务端的接口是其他的,
那么就存在域名或端口号跨域的情况下,但是很幸运的是 devServer有一个叫proxy配置项,可以通过该配置来解决跨域的问题,那是因为 dev-server 使用了 http-proxy-middleware 包(了解该包的更多用法 )。
假如现在我们本地访问的域名是 http://localhost:8081, 但是我现在调用的是百度页面中的一个接口,该接口地址是:http://news.baidu.com/widget?ajax=json&id=ad。现在我们只需要在devServer中的proxy的配置就可以了:
如下配置:
proxy: {
'/api': {
target: 'http://news.baidu.com', // 目标接口的域名
// secure: true, // https 的时候 使用该参数
changeOrigin: true, // 是否跨域
pathRewrite: {
'^/api' : '' // 重写路径
}
}
}
然后我们在main.js里面编写如下代码:
import axios from 'axios';
axios.get('/api/widget?ajax=json&id=ad').then(res => {
console.log(res);
});
在这里请求我使用 axios 插件,其实和jquery是一个意思的。为了方便就用了这个。
下面我们来理解下上面配置的含义:
1. 首先是百度的接口地址是这样的:http://news.baidu.com/widget?ajax=json&id=ad;
2. proxy 的配置项 '/api' 和 target: 'http://news.baidu.com' 的含义是,匹配请求中 /api 含有这样的域名重定向 到 'http://news.baidu.com'来。
因此我在接口地址上 添加了前缀 '/api', 如: axios.get('/api/widget?ajax=json&id=ad'); 因此会自动补充前缀,也就是说,url: '/api/widget?ajax=json&id=ad' 等价
于 url: 'http://news.baidu.com/api/widget?ajax=json&id=ad'.
3. changeOrigin: true/false 还参数值是一个布尔值,含义是 是否需要跨域。
4. secure: true, 如果是https请求就需要改参数配置,需要ssl证书吧。
5. pathRewrite: {'^/api' : ''}的含义是重写url地址,把url的地址里面含有 '/api' 这样的 替换成 '',
因此接口地址就变成了 http://news.baidu.com/widget?ajax=json&id=ad; 因此就可以请求得到了,最后就返回
接口数据了。
*/ /* https://segmentfault.com/a/1190000018987483?utm_source=tag-newest
url-loader 会将引入的图片(还有字体、音视频等)编码,生成dataURl并将其打包到文件中,引入这个dataURL就能访问。如果图片较大,编码会消耗性能。
因此url-loader提供了一个limit参数,小于limit字节的文件会被转为DataURl,大于limit的内部调用file-loader进行copy。
接下来摘取几个重要的属性做说明 outputPath
该属性指明我们最终导出的文件路径
最终导出的文件路径 === output.path + url-loader.outputPath + url-loader.name publicPath(常用于生成环境)
该属性指明我们最终引用的文件路径(打包生成的index.html文件里面引用资源的前缀)
最终引用的文件路径前缀 === output.publicPath + url-loader.publicPath + url-loader.name name
该属性指明文件的最终名称。
同样的,我们可以直接把outputPath的内容写到name中,一样可以生成对应的路径
经过上面的说明,我们得出结论,最终的静态文件路径(图片,音频,视频,字体等)=== output.publicPath + url-loader.publicPath + output.path + url-loader.outputPath + url-loader.name 我们在本地开发的时候都是localhost:8080/下面的根目录,所以当图片生成如下的绝对地址是不会出问题的,可是同样的webpack配置放到生成环境上就不一定了。
因为生成环境大部分的前端静态文件都不是在根目录啊,有可能就是这样的目录结构
www/
+folder/
+static/
+css/
+img/
+js/
+index.html
这样你开发环境的绝对路径放到服务器上面自然就404了,所以要不然用相对路径,要不然就统一写死绝对路径(针对不同环境添加不同的publicPath)
(同样道理,解释为什么css里面的背景图路径404,但是这个解决起来需要用到extract-text-webpack-plugin或者mini-css-extract-plugin这个插件,前者用于webpack4以下版本,后者是4以上版本,配置options里面的publicPath) css 提取插件,`extract-text-webpack-plugin` 插件 或者 `mini-css-extract-plugin` 的 loader也都提供了publicPath,用来复写提取出来的css文件中的资源的引入路径
*/
模板html示例:
<!DOCTYPE html>
<head>
<title><%=htmlWebpackPlugin.options.title %></title>
<meta charset="utf-8" />
<style>
/* 垂直居中显示 */
.center-in-center{
position: absolute;
margin: 10px;
padding: 10px;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
-moz-transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
-o-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
</style>
</head>
<body>
<div id="root" >
</div>
</body>
</html>
解释下版本号:
1.15.2对应就是MAJOR,MINOR.PATCH:1是marjor version;15是minor version;2是patch version。
MAJOR:这个版本号变化了表示有了一个不可以和上个版本兼容的大更改。
MINOR:这个版本号变化了表示有了增加了新的功能,并且可以向后兼容。
PATCH:这个版本号变化了表示修复了bug,并且可以向后兼容。
当我们使用最新的Node运行‘npm instal --save xxx',的时候,他会优先考虑使用插入符号(^)而不是波浪符号(~)了。
波浪符号(~):他会更新到当前minor version(也就是中间的那位数字)中最新的版本。放到我们的例子中就是:body-parser:~1.15.2,这个库会去匹配更新到1.15.x的最新版本,如果出了一个新的版本为1.16.0,则不会自动升级。波浪符号是曾经npm安装时候的默认符号,现在已经变为了插入符号。
插入符号(^):这个符号就显得非常的灵活了,他将会把当前库的版本更新到当前major version(也就是第一位数字)中最新的版本。放到我们的例子中就是:bluebird:^3.3.4,这个库会去匹配3.x.x中最新的版本,但是他不会自动更新到4.0.0。
因为major version变化表示可能会影响之前版本的兼容性,所以无论是波浪符号还是插入符号都不会自动去修改major version,因为这可能导致程序crush,可能需要手动修改代码。